mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-02-16 18:05:19 +00:00
fixed mdm rules (#2764)
* fixed mdm rules * changelog * review feedback jetbrains * review feedback
This commit is contained in:
parent
9500a1a7de
commit
795fb31a3a
@ -0,0 +1,5 @@
|
||||
---
|
||||
type: fix
|
||||
issue: 2764
|
||||
title: "Searches for mdm-expanded references such as Observation?subject:mdm=123 were getting denied by access rules
|
||||
that did not recognize the :mdm suffix. This has been corrected."
|
@ -36,7 +36,6 @@ import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
@ -46,6 +45,7 @@ import org.springframework.batch.item.ItemStreamException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -145,7 +145,7 @@ public class ReverseCronologicalBatchResourcePidReader implements ItemReader<Lis
|
||||
return retval;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Nonnull
|
||||
private SearchParameterMap buildSearchParameterMap(ResourceSearch resourceSearch) {
|
||||
SearchParameterMap map = resourceSearch.getSearchParameterMap();
|
||||
map.setLastUpdated(new DateRangeParam().setUpperBoundInclusive(myThresholdHighByUrlIndex.get(myUrlIndex)));
|
||||
@ -154,7 +154,7 @@ public class ReverseCronologicalBatchResourcePidReader implements ItemReader<Lis
|
||||
return map;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Nonnull
|
||||
private SystemRequestDetails buildSystemRequestDetails() {
|
||||
SystemRequestDetails retval = new SystemRequestDetails();
|
||||
retval.setRequestPartitionId(myPartitionedUrls.get(myUrlIndex).getRequestPartitionId());
|
||||
|
@ -25,14 +25,13 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -41,6 +40,7 @@ import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
public class HapiTransactionService {
|
||||
|
@ -20,11 +20,11 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -34,6 +34,7 @@ import java.util.Set;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.hl7.fhir.instance.model.api.IAnyResource.SP_RES_ID;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
@ -91,10 +92,8 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
|
||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||
|
||||
IBaseResource appliesToResource;
|
||||
Collection<IIdType> appliesToResourceId = null;
|
||||
String appliesToResourceType = null;
|
||||
Map<String, String[]> appliesToSearchParams = null;
|
||||
RuleTarget target = new RuleTarget();
|
||||
|
||||
switch (myOp) {
|
||||
case READ:
|
||||
if (theOutputResource == null) {
|
||||
@ -102,8 +101,8 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
switch (theOperation) {
|
||||
case READ:
|
||||
case VREAD:
|
||||
appliesToResourceId = Collections.singleton(theInputResourceId);
|
||||
appliesToResourceType = theInputResourceId.getResourceType();
|
||||
target.resourceIds = Collections.singleton(theInputResourceId);
|
||||
target.resourceType = theInputResourceId.getResourceType();
|
||||
break;
|
||||
case SEARCH_SYSTEM:
|
||||
case HISTORY_SYSTEM:
|
||||
@ -115,47 +114,28 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
appliesToResourceType = theRequestDetails.getResourceName();
|
||||
appliesToSearchParams = theRequestDetails.getParameters();
|
||||
target.resourceType = theRequestDetails.getResourceName();
|
||||
target.setSearchParams(theRequestDetails);
|
||||
|
||||
/*
|
||||
* If this is a search with an "_id" parameter, we can treat this
|
||||
* as a read for the given resource ID(s)
|
||||
*/
|
||||
if (theRequestDetails.getParameters().containsKey("_id")) {
|
||||
String[] idValues = theRequestDetails.getParameters().get("_id");
|
||||
appliesToResourceId = new ArrayList<>();
|
||||
|
||||
for (String nextIdValue : idValues) {
|
||||
QualifiedParamList orParamList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, nextIdValue);
|
||||
for (String next : orParamList) {
|
||||
IIdType nextId = ctx.getVersion().newIdType().setValue(next);
|
||||
if (nextId.hasIdPart()) {
|
||||
if (!nextId.hasResourceType()) {
|
||||
nextId = nextId.withResourceType(appliesToResourceType);
|
||||
}
|
||||
if (nextId.getResourceType().equals(appliesToResourceType)) {
|
||||
appliesToResourceId.add(nextId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (appliesToResourceId.isEmpty()) {
|
||||
appliesToResourceId = null;
|
||||
}
|
||||
if (theRequestDetails.getParameters().containsKey(SP_RES_ID)) {
|
||||
setTargetFromResourceId(theRequestDetails, ctx, target);
|
||||
}
|
||||
break;
|
||||
case HISTORY_TYPE:
|
||||
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
appliesToResourceType = theRequestDetails.getResourceName();
|
||||
target.resourceType = theRequestDetails.getResourceName();
|
||||
break;
|
||||
case HISTORY_INSTANCE:
|
||||
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
appliesToResourceId = Collections.singleton(theInputResourceId);
|
||||
target.resourceIds = Collections.singleton(theInputResourceId);
|
||||
break;
|
||||
case GET_PAGE:
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
@ -182,9 +162,9 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
appliesToResource = theOutputResource;
|
||||
target.resource = theOutputResource;
|
||||
if (theOutputResource != null) {
|
||||
appliesToResourceId = Collections.singleton(theOutputResource.getIdElement());
|
||||
target.resourceIds = Collections.singleton(theOutputResource.getIdElement());
|
||||
}
|
||||
break;
|
||||
case WRITE:
|
||||
@ -198,15 +178,15 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
case DELETE_TAGS:
|
||||
case META_ADD:
|
||||
case META_DELETE:
|
||||
appliesToResource = theInputResource;
|
||||
target.resource = theInputResource;
|
||||
if (theInputResourceId != null) {
|
||||
appliesToResourceId = Collections.singletonList(theInputResourceId);
|
||||
target.resourceIds = Collections.singletonList(theInputResourceId);
|
||||
}
|
||||
break;
|
||||
case PATCH:
|
||||
appliesToResource = null;
|
||||
target.resource = null;
|
||||
if (theInputResourceId != null) {
|
||||
appliesToResourceId = Collections.singletonList(theInputResourceId);
|
||||
target.resourceIds = Collections.singletonList(theInputResourceId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -220,9 +200,9 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
return null;
|
||||
}
|
||||
if (theOperation == RestOperationTypeEnum.CREATE) {
|
||||
appliesToResource = theInputResource;
|
||||
target.resource = theInputResource;
|
||||
if (theInputResourceId != null) {
|
||||
appliesToResourceId = Collections.singletonList(theInputResourceId);
|
||||
target.resourceIds = Collections.singletonList(theInputResourceId);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
@ -248,13 +228,86 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
|
||||
appliesToResource = theInputResource;
|
||||
appliesToResourceId = Collections.singleton(theInputResourceId);
|
||||
target.resource = theInputResource;
|
||||
target.resourceIds = Collections.singleton(theInputResourceId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case GRAPHQL:
|
||||
return applyRuleToGraphQl(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, thePointcut);
|
||||
case TRANSACTION:
|
||||
return applyRuleToTransaction(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier, thePointcut, ctx);
|
||||
case ALL:
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
case METADATA:
|
||||
if (theOperation == RestOperationTypeEnum.METADATA) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
// Should not happen
|
||||
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
|
||||
}
|
||||
|
||||
switch (myAppliesTo) {
|
||||
case INSTANCES:
|
||||
return applyRuleToInstances(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, target);
|
||||
case ALL_RESOURCES:
|
||||
if (target.resourceType != null) {
|
||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TYPES:
|
||||
if (target.resource != null) {
|
||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||
String type = theRequestDetails.getFhirContext().getResourceType(target.resource);
|
||||
if (myAppliesToTypes.contains(type) == false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target.resourceIds != null) {
|
||||
for (IIdType nextRequestAppliesToResourceId : target.resourceIds) {
|
||||
if (nextRequestAppliesToResourceId.hasResourceType()) {
|
||||
String nextRequestAppliesToResourceIdType = nextRequestAppliesToResourceId.getResourceType();
|
||||
if (myAppliesToTypes.contains(nextRequestAppliesToResourceIdType) == false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target.resourceType != null) {
|
||||
if (!myAppliesToTypes.contains(target.resourceType)) {
|
||||
return null;
|
||||
}
|
||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
} else if (myClassifierType == ClassifierTypeEnum.IN_COMPARTMENT) {
|
||||
// ok we'll check below
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||
}
|
||||
|
||||
switch (myClassifierType) {
|
||||
case ANY_ID:
|
||||
break;
|
||||
case IN_COMPARTMENT:
|
||||
return applyRuleToCompartment(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theFlags, ctx, target);
|
||||
default:
|
||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||
}
|
||||
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Verdict applyRuleToGraphQl(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, Pointcut thePointcut) {
|
||||
if (theOperation == RestOperationTypeEnum.GRAPHQL_REQUEST) {
|
||||
|
||||
// Make sure that the requestor actually has sufficient access to see the given resource
|
||||
@ -266,7 +319,119 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case TRANSACTION:
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Verdict applyRuleToCompartment(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, Set<AuthorizationFlagsEnum> theFlags, FhirContext ctx, RuleTarget target) {
|
||||
FhirTerser t = ctx.newTerser();
|
||||
boolean foundMatch = false;
|
||||
|
||||
if (target.resourceIds != null && target.resourceIds.size() > 0) {
|
||||
boolean haveOwnersForAll = target.resourceIds
|
||||
.stream()
|
||||
.allMatch(n -> myClassifierCompartmentOwners.contains(n.toUnqualifiedVersionless()));
|
||||
if (haveOwnersForAll) {
|
||||
foundMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (IIdType next : myClassifierCompartmentOwners) {
|
||||
if (target.resource != null) {
|
||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, target.resource, next)) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the client has permission to read compartment
|
||||
* Patient/ABC, then a search for Patient?_id=Patient/ABC
|
||||
* should be permitted. This is kind of a one-off case, but
|
||||
* it makes sense.
|
||||
*/
|
||||
if (next.getResourceType().equals(target.resourceType)) {
|
||||
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(target.getSearchParams(), next, SP_RES_ID, theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
if (verdict != null) {
|
||||
return verdict;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're trying to read a resource that could potentially be
|
||||
* in the given compartment, we'll let the request through and
|
||||
* catch any issues on the response.
|
||||
*
|
||||
* This is less than perfect, but it's the best we can do-
|
||||
* If the user is allowed to see compartment "Patient/123" and
|
||||
* the client is requesting to read a CarePlan, there is nothing
|
||||
* in the request URL that indicates whether or not the CarePlan
|
||||
* might be in the given compartment.
|
||||
*/
|
||||
if (isNotBlank(target.resourceType)) {
|
||||
RuntimeResourceDefinition sourceDef = theRequestDetails.getFhirContext().getResourceDefinition(target.resourceType);
|
||||
String compartmentOwnerResourceType = next.getResourceType();
|
||||
if (!StringUtils.equals(target.resourceType, compartmentOwnerResourceType)) {
|
||||
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(compartmentOwnerResourceType);
|
||||
if (!params.isEmpty()) {
|
||||
|
||||
/*
|
||||
* If this is a search, we can at least check whether
|
||||
* the client has requested a search parameter that
|
||||
* would match the given compartment. In this case, this
|
||||
* is a very effective mechanism.
|
||||
*/
|
||||
if (target.getSearchParams() != null && !theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
for (RuntimeSearchParam nextRuntimeSearchParam : params) {
|
||||
String name = nextRuntimeSearchParam.getName();
|
||||
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(target.getSearchParams(), next, name, theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
if (verdict != null) {
|
||||
return verdict;
|
||||
}
|
||||
}
|
||||
} else if (getMode() == PolicyEnum.ALLOW) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundMatch) {
|
||||
return null;
|
||||
}
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Verdict applyRuleToInstances(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, RuleTarget target) {
|
||||
if (target.resourceIds != null && target.resourceIds.size() > 0) {
|
||||
int haveMatches = 0;
|
||||
for (IIdType requestAppliesToResource : target.resourceIds) {
|
||||
|
||||
for (IIdType next : myAppliesToInstances) {
|
||||
if (isNotBlank(next.getResourceType())) {
|
||||
if (!next.getResourceType().equals(requestAppliesToResource.getResourceType())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!next.getIdPart().equals(requestAppliesToResource.getIdPart())) {
|
||||
continue;
|
||||
}
|
||||
haveMatches++;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (haveMatches == target.resourceIds.size()) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Verdict applyRuleToTransaction(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Pointcut thePointcut, FhirContext ctx) {
|
||||
if (!(theOperation == RestOperationTypeEnum.TRANSACTION)) {
|
||||
return null;
|
||||
}
|
||||
@ -372,171 +537,29 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case ALL:
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
case METADATA:
|
||||
if (theOperation == RestOperationTypeEnum.METADATA) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
// Should not happen
|
||||
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
|
||||
}
|
||||
|
||||
switch (myAppliesTo) {
|
||||
case INSTANCES:
|
||||
if (appliesToResourceId != null && appliesToResourceId.size() > 0) {
|
||||
int haveMatches = 0;
|
||||
for (IIdType requestAppliesToResource : appliesToResourceId) {
|
||||
private void setTargetFromResourceId(RequestDetails theRequestDetails, FhirContext ctx, RuleTarget target) {
|
||||
String[] idValues = theRequestDetails.getParameters().get(SP_RES_ID);
|
||||
target.resourceIds = new ArrayList<>();
|
||||
|
||||
for (IIdType next : myAppliesToInstances) {
|
||||
if (isNotBlank(next.getResourceType())) {
|
||||
if (!next.getResourceType().equals(requestAppliesToResource.getResourceType())) {
|
||||
continue;
|
||||
for (String nextIdValue : idValues) {
|
||||
QualifiedParamList orParamList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, nextIdValue);
|
||||
for (String next : orParamList) {
|
||||
IIdType nextId = ctx.getVersion().newIdType().setValue(next);
|
||||
if (nextId.hasIdPart()) {
|
||||
if (!nextId.hasResourceType()) {
|
||||
nextId = nextId.withResourceType(target.resourceType);
|
||||
}
|
||||
}
|
||||
if (!next.getIdPart().equals(requestAppliesToResource.getIdPart())) {
|
||||
continue;
|
||||
}
|
||||
haveMatches++;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (haveMatches == appliesToResourceId.size()) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
case ALL_RESOURCES:
|
||||
if (appliesToResourceType != null) {
|
||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TYPES:
|
||||
if (appliesToResource != null) {
|
||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||
String type = theRequestDetails.getFhirContext().getResourceType(appliesToResource);
|
||||
if (myAppliesToTypes.contains(type) == false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (appliesToResourceId != null) {
|
||||
for (IIdType nextRequestAppliesToResourceId : appliesToResourceId) {
|
||||
if (nextRequestAppliesToResourceId.hasResourceType()) {
|
||||
String nextRequestAppliesToResourceIdType = nextRequestAppliesToResourceId.getResourceType();
|
||||
if (myAppliesToTypes.contains(nextRequestAppliesToResourceIdType) == false) {
|
||||
return null;
|
||||
if (nextId.getResourceType().equals(target.resourceType)) {
|
||||
target.resourceIds.add(nextId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (appliesToResourceType != null) {
|
||||
if (!myAppliesToTypes.contains(appliesToResourceType)) {
|
||||
return null;
|
||||
if (target.resourceIds.isEmpty()) {
|
||||
target.resourceIds = null;
|
||||
}
|
||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
} else if (myClassifierType == ClassifierTypeEnum.IN_COMPARTMENT) {
|
||||
// ok we'll check below
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||
}
|
||||
|
||||
switch (myClassifierType) {
|
||||
case ANY_ID:
|
||||
break;
|
||||
case IN_COMPARTMENT:
|
||||
FhirTerser t = ctx.newTerser();
|
||||
boolean foundMatch = false;
|
||||
|
||||
if (appliesToResourceId != null && appliesToResourceId.size() > 0) {
|
||||
boolean haveOwnersForAll = appliesToResourceId
|
||||
.stream()
|
||||
.allMatch(n -> myClassifierCompartmentOwners.contains(n.toUnqualifiedVersionless()));
|
||||
if (haveOwnersForAll) {
|
||||
foundMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (IIdType next : myClassifierCompartmentOwners) {
|
||||
if (appliesToResource != null) {
|
||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesToResource, next)) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the client has permission to read compartment
|
||||
* Patient/ABC, then a search for Patient?_id=Patient/ABC
|
||||
* should be permitted. This is kind of a one-off case, but
|
||||
* it makes sense.
|
||||
*/
|
||||
if (next.getResourceType().equals(appliesToResourceType)) {
|
||||
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(appliesToSearchParams, next, IAnyResource.SP_RES_ID, theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
if (verdict != null) {
|
||||
return verdict;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're trying to read a resource that could potentially be
|
||||
* in the given compartment, we'll let the request through and
|
||||
* catch any issues on the response.
|
||||
*
|
||||
* This is less than perfect, but it's the best we can do-
|
||||
* If the user is allowed to see compartment "Patient/123" and
|
||||
* the client is requesting to read a CarePlan, there is nothing
|
||||
* in the request URL that indicates whether or not the CarePlan
|
||||
* might be in the given compartment.
|
||||
*/
|
||||
if (isNotBlank(appliesToResourceType)) {
|
||||
RuntimeResourceDefinition sourceDef = theRequestDetails.getFhirContext().getResourceDefinition(appliesToResourceType);
|
||||
String compartmentOwnerResourceType = next.getResourceType();
|
||||
if (!StringUtils.equals(appliesToResourceType, compartmentOwnerResourceType)) {
|
||||
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(compartmentOwnerResourceType);
|
||||
if (!params.isEmpty()) {
|
||||
|
||||
/*
|
||||
* If this is a search, we can at least check whether
|
||||
* the client has requested a search parameter that
|
||||
* would match the given compartment. In this case, this
|
||||
* is a very effective mechanism.
|
||||
*/
|
||||
if (appliesToSearchParams != null && !theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||
for (RuntimeSearchParam nextRuntimeSearchParam : params) {
|
||||
String name = nextRuntimeSearchParam.getName();
|
||||
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(appliesToSearchParams, next, name, theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
if (verdict != null) {
|
||||
return verdict;
|
||||
}
|
||||
}
|
||||
} else if (getMode() == PolicyEnum.ALLOW) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundMatch) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||
}
|
||||
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
|
||||
private Verdict checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(Map<String, String[]> theSearchParams, IIdType theCompartmentOwner, String theSearchParamName, RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource) {
|
||||
|
@ -0,0 +1,38 @@
|
||||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RuleTarget {
|
||||
IBaseResource resource;
|
||||
Collection<IIdType> resourceIds = null;
|
||||
String resourceType = null;
|
||||
private Map<String, String[]> mySearchParams = null;
|
||||
|
||||
public Map<String, String[]> getSearchParams() {
|
||||
return mySearchParams;
|
||||
}
|
||||
|
||||
public void setSearchParams(RequestDetails theRequestDetails) {
|
||||
mySearchParams = stripMdmSuffix(theRequestDetails.getParameters());
|
||||
}
|
||||
|
||||
private Map<String, String[]> stripMdmSuffix(Map<String, String[]> theParameters) {
|
||||
Map<String, String[]> retval = new HashMap<>();
|
||||
for (Map.Entry<String, String[]> entry : theParameters.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String[] value = entry.getValue();
|
||||
if (key.endsWith(Constants.PARAMQUALIFIER_MDM)) {
|
||||
key = key.split(Constants.PARAMQUALIFIER_MDM)[0];
|
||||
}
|
||||
retval.put(key, value);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class RuleTargetTest {
|
||||
|
||||
@Test
|
||||
void setSearchParams() {
|
||||
RuleTarget target = new RuleTarget();
|
||||
RequestDetails requestDetails = new ServletRequestDetails();
|
||||
requestDetails.addParameter("subject:mdm", new String[]{"Patient/123"});
|
||||
requestDetails.addParameter("performer", new String[]{"Practioner/456"});
|
||||
target.setSearchParams(requestDetails);
|
||||
|
||||
Map<String, String[]> storedParams = target.getSearchParams();
|
||||
assertThat(storedParams.keySet(), hasSize(2));
|
||||
assertEquals("Patient/123", storedParams.get("subject")[0]);
|
||||
assertEquals("Practioner/456", storedParams.get("performer")[0]);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user