Auth interceptor early checks (#995)

* Validator fix

* Enhance AuthorizationInterceptor so that it tries to deny access earlier
for compartment searches that are outside the allowable compartment.

* FIx DSTU2 test that wasn't updated

* More test fixes
This commit is contained in:
James Agnew 2018-06-10 17:48:20 -04:00 committed by GitHub
parent 903df68d2a
commit cc0e836680
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1680 additions and 2592 deletions

View File

@ -47,7 +47,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou
* See #778 * See #778
*/ */
@Test @Test
public void testReadingObservationAccessRight() throws IOException { public void testReadingObservationAccessRight() {
Practitioner practitioner1 = new Practitioner(); Practitioner practitioner1 = new Practitioner();
final IIdType practitionerId1 = ourClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless(); final IIdType practitionerId1 = ourClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless();
@ -105,7 +105,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou
* See #667 * See #667
*/ */
@Test @Test
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException { public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() {
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.setActive(true); pt1.setActive(true);
final IIdType pid1 = ourClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless(); final IIdType pid1 = ourClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless();

View File

@ -44,7 +44,7 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
* See #778 * See #778
*/ */
@Test @Test
public void testReadingObservationAccessRight() throws IOException { public void testReadingObservationAccessRight() {
Practitioner practitioner1 = new Practitioner(); Practitioner practitioner1 = new Practitioner();
final IIdType practitionerId1 = myClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless(); final IIdType practitionerId1 = myClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless();
@ -102,7 +102,7 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
* See #667 * See #667
*/ */
@Test @Test
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException { public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() {
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.setActive(true); pt1.setActive(true);
final IIdType pid1 = myClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless(); final IIdType pid1 = myClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless();

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import java.util.Collection;
/**
* @see AuthorizationInterceptor#setFlags(Collection)
*/
public enum AuthorizationFlagsEnum {
/**
* If this flag is set, attempts to perform read operations
* (read/search/history) will be matched by the interceptor before
* the method handler is called.
* <p>
* For example, suppose a rule set is in place that only allows read
* access to compartment <code>Patient/123</code>. With this flag set,
* any attempts
* to perform a FHIR read/search/history operation will be permitted
* to proceed to the method handler, and responses will be blocked
* by the AuthorizationInterceptor if the response contains a resource
* that is not in the given compartment.
* </p>
* <p>
* Setting this flag is less secure, since the interceptor can potentially leak
* information about the existence of data, but it is useful in some
* scenarios.
* </p>
*
* @since This flag has existed since HAPI FHIR 3.5.0. Prior to this
* version, this flag was the default and there was no ability to
* proactively block compartment read access.
*/
NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS;
}

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.Validate; 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;
@ -35,12 +36,12 @@ import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.defaultString;
@ -56,9 +57,10 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
*/ */
public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter implements IRuleApplier { public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter implements IRuleApplier {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AuthorizationInterceptor.class); private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptor.class);
private PolicyEnum myDefaultPolicy = PolicyEnum.DENY; private PolicyEnum myDefaultPolicy = PolicyEnum.DENY;
private Set<AuthorizationFlagsEnum> myFlags = Collections.emptySet();
/** /**
* Constructor * Constructor
@ -92,11 +94,12 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId,
IBaseResource theOutputResource) { IBaseResource theOutputResource) {
List<IAuthRule> rules = buildRuleList(theRequestDetails); List<IAuthRule> rules = buildRuleList(theRequestDetails);
Set<AuthorizationFlagsEnum> flags = getFlags();
ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation); ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation);
Verdict verdict = null; Verdict verdict = null;
for (IAuthRule nextRule : rules) { for (IAuthRule nextRule : rules) {
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, this); verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, this, flags);
if (verdict != null) { if (verdict != null) {
ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision()); ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision());
break; break;
@ -105,7 +108,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
if (verdict == null) { if (verdict == null) {
ourLog.trace("No rules returned a decision, applying default {}", myDefaultPolicy); ourLog.trace("No rules returned a decision, applying default {}", myDefaultPolicy);
return new Verdict(myDefaultPolicy, null); return new Verdict(getDefaultPolicy(), null);
} }
return verdict; return verdict;
@ -206,6 +209,28 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
myDefaultPolicy = theDefaultPolicy; myDefaultPolicy = theDefaultPolicy;
} }
/**
* This property configures any flags affecting how authorization is
* applied. By default no flags are applied.
*
* @see #setFlags(Collection)
*/
public Set<AuthorizationFlagsEnum> getFlags() {
return Collections.unmodifiableSet(myFlags);
}
/**
* This property configures any flags affecting how authorization is
* applied. By default no flags are applied.
*
* @param theFlags The flags (must not be null)
* @see #setFlags(Collection)
*/
public AuthorizationInterceptor setFlags(AuthorizationFlagsEnum... theFlags) {
Validate.notNull(theFlags, "theFlags must not be null");
return setFlags(Lists.newArrayList(theFlags));
}
/** /**
* Handle an access control verdict of {@link PolicyEnum#DENY}. * Handle an access control verdict of {@link PolicyEnum#DENY}.
* <p> * <p>
@ -325,6 +350,19 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
handleUserOperation(theRequest, theNewResource, RestOperationTypeEnum.UPDATE); handleUserOperation(theRequest, theNewResource, RestOperationTypeEnum.UPDATE);
} }
/**
* This property configures any flags affecting how authorization is
* applied. By default no flags are applied.
*
* @param theFlags The flags (must not be null)
* @see #setFlags(AuthorizationFlagsEnum...)
*/
public AuthorizationInterceptor setFlags(Collection<AuthorizationFlagsEnum> theFlags) {
Validate.notNull(theFlags, "theFlags must not be null");
myFlags = new HashSet<>(theFlags);
return this;
}
private static UnsupportedOperationException failForDstu1() { private static UnsupportedOperationException failForDstu1() {
return new UnsupportedOperationException("Use of this interceptor on DSTU1 servers is not supportd"); return new UnsupportedOperationException("Use of this interceptor on DSTU1 servers is not supportd");
} }

View File

@ -27,6 +27,15 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict; import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
import java.util.Set;
/**
* Note: At this time, this interface is considered internal API to HAPI FHIR,
* and is subject to change without warning. Create your own implementations at
* your own risk. If you have use cases that are not met by the current
* implementation, please consider raising them on the HAPI FHIR
* Google Group.
*/
public interface IAuthRule { public interface IAuthRule {
/** /**
@ -44,9 +53,10 @@ public interface IAuthRule {
* @param theRuleApplier * @param theRuleApplier
* The rule applying module (this can be used by rules to apply the rule set to * The rule applying module (this can be used by rules to apply the rule set to
* nested objects in the request, such as nested requests in a transaction) * nested objects in the request, such as nested requests in a transaction)
* @param theFlags
* @return Returns a policy decision, or <code>null</code> if the rule does not apply * @return Returns a policy decision, or <code>null</code> if the rule does not apply
*/ */
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier); Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags);
/** /**
* Returns a name for this rule, to be used in logs and error messages * Returns a name for this rule, to be used in logs and error messages

View File

@ -29,10 +29,11 @@ import org.hl7.fhir.instance.model.api.IIdType;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
class OperationRule extends BaseRule implements IAuthRule { class OperationRule extends BaseRule implements IAuthRule {
private RuleBuilder.ITenantApplicabilityChecker myTenentApplicabilityChecker; private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
private String myOperationName; private String myOperationName;
private boolean myAppliesToServer; private boolean myAppliesToServer;
private HashSet<Class<? extends IBaseResource>> myAppliesToTypes; private HashSet<Class<? extends IBaseResource>> myAppliesToTypes;
@ -75,17 +76,25 @@ class OperationRule extends BaseRule implements IAuthRule {
} }
@Override @Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier) { public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
FhirContext ctx = theRequestDetails.getServer().getFhirContext(); FhirContext ctx = theRequestDetails.getServer().getFhirContext();
if (myTenentApplicabilityChecker != null) { if (myTenantApplicabilityChecker != null) {
if (!myTenentApplicabilityChecker.applies(theRequestDetails)) { if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
return null; return null;
} }
} }
boolean applies = false; boolean applies = false;
switch (theOperation) { switch (theOperation) {
case ADD_TAGS:
case DELETE_TAGS:
case GET_TAGS:
case GET_PAGE:
case GRAPHQL_REQUEST:
// These things can't be tracked by the AuthorizationInterceptor
// at this time
return null;
case EXTENDED_OPERATION_SERVER: case EXTENDED_OPERATION_SERVER:
if (myAppliesToServer || myAppliesAtAnyLevel) { if (myAppliesToServer || myAppliesAtAnyLevel) {
applies = true; applies = true;
@ -130,6 +139,40 @@ class OperationRule extends BaseRule implements IAuthRule {
} }
} }
break; break;
case CREATE:
break;
case DELETE:
break;
case HISTORY_INSTANCE:
break;
case HISTORY_SYSTEM:
break;
case HISTORY_TYPE:
break;
case READ:
break;
case SEARCH_SYSTEM:
break;
case SEARCH_TYPE:
break;
case TRANSACTION:
break;
case UPDATE:
break;
case VALIDATE:
break;
case VREAD:
break;
case METADATA:
break;
case META_ADD:
break;
case META:
break;
case META_DELETE:
break;
case PATCH:
break;
default: default:
return null; return null;
} }
@ -160,8 +203,8 @@ class OperationRule extends BaseRule implements IAuthRule {
myOperationName = theOperationName; myOperationName = theOperationName;
} }
public void setTenentApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenentApplicabilityChecker) { public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
myTenentApplicabilityChecker = theTenentApplicabilityChecker; myTenantApplicabilityChecker = theTenantApplicabilityChecker;
} }
} }

View File

@ -174,7 +174,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
myOpRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker); myOpRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
} }
if (myOperationRule != null) { if (myOperationRule != null) {
myOperationRule.setTenentApplicabilityChecker(myTenantApplicabilityChecker); myOperationRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
} }
} }

View File

@ -41,7 +41,7 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
@Override @Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
IRuleApplier theRuleApplier) { IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
if (theInputResourceId != null) { if (theInputResourceId != null) {
return null; return null;

View File

@ -1,5 +1,28 @@
package ca.uhn.fhir.rest.server.interceptor.auth; package ca.uhn.fhir.rest.server.interceptor.auth;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
import ca.uhn.fhir.util.FhirTerser;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
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 java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
/* /*
@ -22,26 +45,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L% * #L%
*/ */
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
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 ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
import ca.uhn.fhir.util.FhirTerser;
class RuleImplOp extends BaseRule /* implements IAuthRule */ { class RuleImplOp extends BaseRule /* implements IAuthRule */ {
private AppliesTypeEnum myAppliesTo; private AppliesTypeEnum myAppliesTo;
@ -54,13 +57,16 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
private List<IIdType> myAppliesToInstances; private List<IIdType> myAppliesToInstances;
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker; private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
/**
* Constructor
*/
public RuleImplOp(String theRuleName) { public RuleImplOp(String theRuleName) {
super(theRuleName); super(theRuleName);
} }
@Override @Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
IRuleApplier theRuleApplier) { IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
if (myTenantApplicabilityChecker != null) { if (myTenantApplicabilityChecker != null) {
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) { if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
@ -73,9 +79,14 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
IBaseResource appliesToResource; IBaseResource appliesToResource;
IIdType appliesToResourceId = null; IIdType appliesToResourceId = null;
String appliesToResourceType = null; String appliesToResourceType = null;
Map<String, String[]> appliesToSearchParams = null;
switch (myOp) { switch (myOp) {
case READ: case READ:
if (theOutputResource == null) { if (theOutputResource == null) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
switch (theOperation) { switch (theOperation) {
case READ: case READ:
case VREAD: case VREAD:
@ -83,14 +94,51 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
appliesToResourceType = theInputResourceId.getResourceType(); appliesToResourceType = theInputResourceId.getResourceType();
break; break;
case SEARCH_SYSTEM: case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_INSTANCE:
case HISTORY_SYSTEM: case HISTORY_SYSTEM:
case HISTORY_TYPE: if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.ALLOW, this); return new Verdict(PolicyEnum.ALLOW, this);
}
break;
case SEARCH_TYPE:
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
return new Verdict(PolicyEnum.ALLOW, this);
}
appliesToResourceType = theRequestDetails.getResourceName();
appliesToSearchParams = theRequestDetails.getParameters();
break;
case HISTORY_TYPE:
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
return new Verdict(PolicyEnum.ALLOW, this);
}
appliesToResourceType = theRequestDetails.getResourceName();
break;
case HISTORY_INSTANCE:
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
return new Verdict(PolicyEnum.ALLOW, this);
}
appliesToResourceId = theInputResourceId;
break;
case GET_PAGE:
return new Verdict(PolicyEnum.ALLOW, this);
// None of the following are checked on the way in
case ADD_TAGS:
case DELETE_TAGS:
case GET_TAGS:
case GRAPHQL_REQUEST:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:
case EXTENDED_OPERATION_INSTANCE:
case CREATE:
case DELETE:
case TRANSACTION:
case UPDATE:
case VALIDATE:
case METADATA:
case META_ADD:
case META:
case META_DELETE:
case PATCH:
default: default:
return null; return null;
} }
@ -244,28 +292,38 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) { if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null; return null;
} }
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
return new Verdict(PolicyEnum.ALLOW, this); return new Verdict(PolicyEnum.ALLOW, this);
} }
}
break; break;
case TYPES: case TYPES:
if (appliesToResource != null) { if (appliesToResource != null) {
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) { if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
return null; return null;
} }
} }
}
// if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
if (appliesToResourceId != null && appliesToResourceId.hasResourceType()) { if (appliesToResourceId != null && appliesToResourceId.hasResourceType()) {
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass(); Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
if (myAppliesToTypes.contains(type) == false) { if (myAppliesToTypes.contains(type) == false) {
return null; return null;
} }
} }
// }
if (appliesToResourceType != null) { if (appliesToResourceType != null) {
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceType).getImplementingClass(); Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceType).getImplementingClass();
if (myAppliesToTypes.contains(type)) { if (myAppliesToTypes.contains(type)) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) { if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null; return null;
} }
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
return new Verdict(PolicyEnum.ALLOW, this); return new Verdict(PolicyEnum.ALLOW, this);
} else if (myClassifierType == ClassifierTypeEnum.IN_COMPARTMENT) {
// ok we'll check below
}
} }
} }
break; break;
@ -292,6 +350,49 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
break; break;
} }
} }
/*
* 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() == false) {
/*
* 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[] values = appliesToSearchParams.get(nextRuntimeSearchParam.getName());
if (values != null) {
for (String nextParameterValue : values) {
if (nextParameterValue.equals(next.getValue())) {
return new Verdict(PolicyEnum.ALLOW, this);
}
}
}
}
} else {
return new Verdict(PolicyEnum.ALLOW, this);
}
break;
}
}
}
} }
if (!foundMatch) { if (!foundMatch) {
return null; return null;
@ -308,22 +409,12 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
return newVerdict(); return newVerdict();
} }
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) { public TransactionAppliesToEnum getTransactionAppliesToOp() {
myTenantApplicabilityChecker = theTenantApplicabilityChecker; return myTransactionAppliesToOp;
} }
@Override public void setTransactionAppliesToOp(TransactionAppliesToEnum theOp) {
public String toString() { myTransactionAppliesToOp = theOp;
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
builder.append("op", myOp);
builder.append("transactionAppliesToOp", myTransactionAppliesToOp);
builder.append("appliesTo", myAppliesTo);
builder.append("appliesToTypes", myAppliesToTypes);
builder.append("appliesToTenant", myTenantApplicabilityChecker);
builder.append("classifierCompartmentName", myClassifierCompartmentName);
builder.append("classifierCompartmentOwners", myClassifierCompartmentOwners);
builder.append("classifierType", myClassifierType);
return builder.toString();
} }
private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) { private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) {
@ -343,14 +434,14 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
} }
} }
public TransactionAppliesToEnum getTransactionAppliesToOp() {
return myTransactionAppliesToOp;
}
public void setAppliesTo(AppliesTypeEnum theAppliesTo) { public void setAppliesTo(AppliesTypeEnum theAppliesTo) {
myAppliesTo = theAppliesTo; myAppliesTo = theAppliesTo;
} }
public void setAppliesToInstances(List<IIdType> theAppliesToInstances) {
myAppliesToInstances = theAppliesToInstances;
}
public void setAppliesToTypes(Set<?> theAppliesToTypes) { public void setAppliesToTypes(Set<?> theAppliesToTypes) {
myAppliesToTypes = theAppliesToTypes; myAppliesToTypes = theAppliesToTypes;
} }
@ -372,12 +463,22 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
return this; return this;
} }
public void setTransactionAppliesToOp(TransactionAppliesToEnum theOp) { public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
myTransactionAppliesToOp = theOp; myTenantApplicabilityChecker = theTenantApplicabilityChecker;
} }
public void setAppliesToInstances(List<IIdType> theAppliesToInstances) { @Override
myAppliesToInstances = theAppliesToInstances; public String toString() {
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
builder.append("op", myOp);
builder.append("transactionAppliesToOp", myTransactionAppliesToOp);
builder.append("appliesTo", myAppliesTo);
builder.append("appliesToTypes", myAppliesToTypes);
builder.append("appliesToTenant", myTenantApplicabilityChecker);
builder.append("classifierCompartmentName", myClassifierCompartmentName);
builder.append("classifierCompartmentOwners", myClassifierCompartmentOwners);
builder.append("classifierType", myClassifierType);
return builder.toString();
} }
} }

View File

@ -15,6 +15,7 @@ import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.rest.param.ReferenceParam;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -65,7 +66,7 @@ public class AuthorizationInterceptorDstu2Test {
@Before @Before
public void before() { public void before() {
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.NEVER); ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.NEVER);
for (IServerInterceptor next : new ArrayList<IServerInterceptor>(ourServlet.getInterceptors())) { for (IServerInterceptor next : new ArrayList<>(ourServlet.getInterceptors())) {
ourServlet.unregisterInterceptor(next); ourServlet.unregisterInterceptor(next);
} }
ourReturn = null; ourReturn = null;
@ -1166,8 +1167,8 @@ public class AuthorizationInterceptorDstu2Test {
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
extractResponseAndClose(status); extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertFalse(ourHitMethod);
} }
@ -1177,7 +1178,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1")) .allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdDt("Patient/1"))
.build(); .build();
} }
}); });
@ -1187,13 +1188,13 @@ public class AuthorizationInterceptorDstu2Test {
String respString; String respString;
Bundle respBundle; Bundle respBundle;
ourReturn = new ArrayList<IResource>(); ourReturn = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
ourReturn.add(createPatient(1)); ourReturn.add(createObservation(i, "Patient/1"));
} }
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status); respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
@ -1201,7 +1202,7 @@ public class AuthorizationInterceptorDstu2Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size()); assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal().intValue()); assertEquals(10, respBundle.getTotal().intValue());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next")); assertNotNull(respBundle.getLink("next"));
// Load next page // Load next page
@ -1215,7 +1216,7 @@ public class AuthorizationInterceptorDstu2Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size()); assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal().intValue()); assertEquals(10, respBundle.getTotal().intValue());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); assertEquals("Observation/5", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNull(respBundle.getLink("next")); assertNull(respBundle.getLink("next"));
} }
@ -1226,7 +1227,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1")) .allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdDt("Patient/1"))
.build(); .build();
} }
}); });
@ -1236,16 +1237,16 @@ public class AuthorizationInterceptorDstu2Test {
String respString; String respString;
Bundle respBundle; Bundle respBundle;
ourReturn = new ArrayList<IResource>(); ourReturn = new ArrayList<>();
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(1)); ourReturn.add(createObservation(i, "Patient/1"));
} }
for (int i = 0; i < 5; i++) { for (int i = 5; i < 10; i++) {
ourReturn.add(createPatient(2)); ourReturn.add(createObservation(i, "Patient/2"));
} }
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status); respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
@ -1253,7 +1254,7 @@ public class AuthorizationInterceptorDstu2Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size()); assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal().intValue()); assertEquals(10, respBundle.getTotal().intValue());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next")); assertNotNull(respBundle.getLink("next"));
// Load next page // Load next page
@ -1261,7 +1262,7 @@ public class AuthorizationInterceptorDstu2Test {
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet(respBundle.getLink("next").getUrl()); httpGet = new HttpGet(respBundle.getLink("next").getUrl());
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status); extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode()); assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod); assertFalse(ourHitMethod);
@ -1283,7 +1284,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpResponse status; HttpResponse status;
String response; String response;
ourReturn = Arrays.asList(createPatient(2)); ourReturn = Collections.singletonList(createPatient(2));
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
@ -1291,9 +1292,9 @@ public class AuthorizationInterceptorDstu2Test {
ourLog.info(response); ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)")); assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode()); assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertFalse(ourHitMethod);
ourReturn = Arrays.asList(createObservation(10, "Patient/2")); ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
@ -1303,7 +1304,7 @@ public class AuthorizationInterceptorDstu2Test {
assertEquals(403, status.getStatusLine().getStatusCode()); assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertTrue(ourHitMethod);
ourReturn = Arrays.asList(createCarePlan(10, "Patient/2")); ourReturn = Collections.singletonList(createCarePlan(10, "Patient/2"));
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10"); httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
@ -1321,7 +1322,7 @@ public class AuthorizationInterceptorDstu2Test {
ourLog.info(response); ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)")); assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode()); assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertFalse(ourHitMethod);
ourReturn = Arrays.asList(createPatient(2), createObservation(10, "Patient/1")); ourReturn = Arrays.asList(createPatient(2), createObservation(10, "Patient/1"));
ourHitMethod = false; ourHitMethod = false;
@ -1331,7 +1332,7 @@ public class AuthorizationInterceptorDstu2Test {
ourLog.info(response); ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)")); assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode()); assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertFalse(ourHitMethod);
} }
@ -1359,7 +1360,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpPost httpPost; HttpPost httpPost;
HttpResponse status; HttpResponse status;
ourReturn = Arrays.asList((IResource) output); ourReturn = Collections.singletonList(output);
ourHitMethod = false; ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/"); httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input)); httpPost.setEntity(createFhirResourceEntity(input));
@ -1419,6 +1420,7 @@ public class AuthorizationInterceptorDstu2Test {
httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/1"))); httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/1")));
status = ourClient.execute(httpPost); status = ourClient.execute(httpPost);
response = extractResponseAndClose(status); response = extractResponseAndClose(status);
ourLog.debug(response);
assertEquals(201, status.getStatusLine().getStatusCode()); assertEquals(201, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertTrue(ourHitMethod);
} }
@ -1469,7 +1471,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpDelete httpDelete; HttpDelete httpDelete;
HttpResponse status; HttpResponse status;
ourReturn = Arrays.asList(createPatient(1)); ourReturn = Collections.singletonList(createPatient(1));
ourHitMethod = false; ourHitMethod = false;
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar"); httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar");
@ -1497,7 +1499,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpDelete httpDelete; HttpDelete httpDelete;
HttpResponse status; HttpResponse status;
ourReturn = Arrays.asList(createPatient(1)); ourReturn = Collections.singletonList(createPatient(1));
ourHitMethod = false; ourHitMethod = false;
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar"); httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar");
@ -1525,7 +1527,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpResponse status; HttpResponse status;
ourHitMethod = false; ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2)); ourReturn = Collections.singletonList(createPatient(2));
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/2"); httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpDelete); status = ourClient.execute(httpDelete);
extractResponseAndClose(status); extractResponseAndClose(status);
@ -1533,7 +1535,7 @@ public class AuthorizationInterceptorDstu2Test {
assertTrue(ourHitMethod); assertTrue(ourHitMethod);
ourHitMethod = false; ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(1)); ourReturn = Collections.singletonList(createPatient(1));
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/1"); httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/1");
status = ourClient.execute(httpDelete); status = ourClient.execute(httpDelete);
extractResponseAndClose(status); extractResponseAndClose(status);
@ -1665,6 +1667,7 @@ public class AuthorizationInterceptorDstu2Test {
httpPost.setEntity(createFhirResourceEntity(createPatient(null))); httpPost.setEntity(createFhirResourceEntity(createPatient(null)));
status = ourClient.execute(httpPost); status = ourClient.execute(httpPost);
response = extractResponseAndClose(status); response = extractResponseAndClose(status);
ourLog.debug(response);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertTrue(ourHitMethod);
@ -1703,6 +1706,7 @@ public class AuthorizationInterceptorDstu2Test {
httpPost.setEntity(createFhirResourceEntity(createPatient(null))); httpPost.setEntity(createFhirResourceEntity(createPatient(null)));
status = ourClient.execute(httpPost); status = ourClient.execute(httpPost);
response = extractResponseAndClose(status); response = extractResponseAndClose(status);
ourLog.debug(response);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertTrue(ourHitMethod);
@ -1718,7 +1722,7 @@ public class AuthorizationInterceptorDstu2Test {
} }
@Test @Test
public void testInvalidInstanceIds() throws Exception { public void testInvalidInstanceIds() {
try { try {
new RuleBuilder().allow("Rule 1").write().instance((String) null); new RuleBuilder().allow("Rule 1").write().instance((String) null);
fail(); fail();
@ -2009,7 +2013,7 @@ public class AuthorizationInterceptorDstu2Test {
} }
@Search() @Search()
public List<IResource> search() { public List<IResource> search(@OptionalParam(name="subject")ReferenceParam theSubject) {
ourHitMethod = true; ourHitMethod = true;
return ourReturn; return ourReturn;
} }

View File

@ -8,9 +8,11 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.IRequestOperationCallback; import ca.uhn.fhir.rest.api.server.IRequestOperationCallback;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.*; import ca.uhn.fhir.rest.server.interceptor.auth.*;
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
@ -287,6 +289,68 @@ public class AuthorizationInterceptorR4Test {
} }
@Test
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder().allow().read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/123")).andThen().denyAll()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
Patient patient;
CarePlan carePlan;
// Unqualified
patient = new Patient();
patient.setId("123");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Qualified
patient = new Patient();
patient.setId("Patient/123");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Wrong one
patient = new Patient();
patient.setId("456");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
/** /**
* #528 * #528
*/ */
@ -390,69 +454,6 @@ public class AuthorizationInterceptorR4Test {
assertTrue(ourHitMethod); assertTrue(ourHitMethod);
} }
@Test
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder().allow().read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/123")).andThen().denyAll()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
Patient patient;
CarePlan carePlan;
// Unqualified
patient = new Patient();
patient.setId("123");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Qualified
patient = new Patient();
patient.setId("Patient/123");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Wrong one
patient = new Patient();
patient.setId("456");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@Test @Test
public void testBatchWhenOnlyTransactionAllowed() throws Exception { public void testBatchWhenOnlyTransactionAllowed() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -477,7 +478,7 @@ public class AuthorizationInterceptorR4Test {
HttpPost httpPost; HttpPost httpPost;
HttpResponse status; HttpResponse status;
ourReturn = Collections.singletonList((Resource) output); ourReturn = Collections.singletonList(output);
ourHitMethod = false; ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/"); httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input)); httpPost.setEntity(createFhirResourceEntity(input));
@ -510,7 +511,7 @@ public class AuthorizationInterceptorR4Test {
HttpPost httpPost; HttpPost httpPost;
HttpResponse status; HttpResponse status;
ourReturn = Collections.singletonList((Resource) output); ourReturn = Collections.singletonList(output);
ourHitMethod = false; ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/"); httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input)); httpPost.setEntity(createFhirResourceEntity(input));
@ -544,7 +545,7 @@ public class AuthorizationInterceptorR4Test {
HttpResponse status; HttpResponse status;
String response; String response;
ourReturn = Collections.singletonList((Resource) output); ourReturn = Collections.singletonList(output);
ourHitMethod = false; ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/"); httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input)); httpPost.setEntity(createFhirResourceEntity(input));
@ -1841,13 +1842,86 @@ public class AuthorizationInterceptorR4Test {
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
extractResponseAndClose(status); extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertFalse(ourHitMethod);
} }
@Test @Test
public void testReadByCompartmentWrong() throws Exception { public void testReadByCompartmentWrongAllTypesProactiveBlockEnabledNoResponse() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
.build();
}
}.setFlags());
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.emptyList();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(404, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(404, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/999/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testReadByCompartmentWrongProactiveBlockDisabled() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
@ -1856,7 +1930,7 @@ public class AuthorizationInterceptorR4Test {
.allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")) .allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build(); .build();
} }
}); }.setFlags(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS));
HttpGet httpGet; HttpGet httpGet;
HttpResponse status; HttpResponse status;
@ -1870,7 +1944,7 @@ public class AuthorizationInterceptorR4Test {
ourLog.info(response); ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)")); assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode()); assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertFalse(ourHitMethod);
ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
ourHitMethod = false; ourHitMethod = false;
@ -1914,6 +1988,133 @@ public class AuthorizationInterceptorR4Test {
} }
@Test
public void testReadByCompartmentWrongProactiveBlockDisabledNoResponse() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen()
.allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build();
}
}.setFlags(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS));
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.emptyList();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(404, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@Test
public void testReadByCompartmentWrongProactiveBlockEnabledNoResponse() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen()
.allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build();
}
}.setFlags());
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.emptyList();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(404, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// CarePlan could potentially be in the Patient/1 compartment but we don't
// have any rules explicitly allowing CarePlan so it's blocked
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/999/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test @Test
public void testReadByInstance() throws Exception { public void testReadByInstance() throws Exception {
ourConditionalCreateId = "1"; ourConditionalCreateId = "1";
@ -1965,7 +2166,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")) .allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build(); .build();
} }
}); });
@ -1977,11 +2178,11 @@ public class AuthorizationInterceptorR4Test {
ourReturn = new ArrayList<>(); ourReturn = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
ourReturn.add(createPatient(1)); ourReturn.add(createObservation(i, "Patient/1"));
} }
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status); respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
@ -1989,7 +2190,7 @@ public class AuthorizationInterceptorR4Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size()); assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal()); assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next")); assertNotNull(respBundle.getLink("next"));
// Load next page // Load next page
@ -2003,7 +2204,7 @@ public class AuthorizationInterceptorR4Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size()); assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal()); assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); assertEquals("Observation/5", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNull(respBundle.getLink("next")); assertNull(respBundle.getLink("next"));
} }
@ -2014,7 +2215,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")) .allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build(); .build();
} }
}); });
@ -2026,14 +2227,14 @@ public class AuthorizationInterceptorR4Test {
ourReturn = new ArrayList<>(); ourReturn = new ArrayList<>();
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(1)); ourReturn.add(createObservation(i, "Patient/1"));
} }
for (int i = 0; i < 5; i++) { for (int i = 5; i < 10; i++) {
ourReturn.add(createPatient(2)); ourReturn.add(createObservation(i, "Patient/2"));
} }
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status); respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
@ -2041,7 +2242,7 @@ public class AuthorizationInterceptorR4Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size()); assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal()); assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next")); assertNotNull(respBundle.getLink("next"));
// Load next page // Load next page
@ -2078,7 +2279,7 @@ public class AuthorizationInterceptorR4Test {
Bundle input = createTransactionWithPlaceholdersRequestBundle(); Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle(); Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output); ourReturn = Collections.singletonList(output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/"); HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input)); httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost); CloseableHttpResponse status = ourClient.execute(httpPost);
@ -2111,7 +2312,7 @@ public class AuthorizationInterceptorR4Test {
Bundle input = createTransactionWithPlaceholdersRequestBundle(); Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle(); Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output); ourReturn = Collections.singletonList(output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/"); HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input)); httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost); CloseableHttpResponse status = ourClient.execute(httpPost);
@ -2146,7 +2347,7 @@ public class AuthorizationInterceptorR4Test {
HttpPost httpPost; HttpPost httpPost;
HttpResponse status; HttpResponse status;
ourReturn = Collections.singletonList((Resource) output); ourReturn = Collections.singletonList(output);
ourHitMethod = false; ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/"); httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input)); httpPost.setEntity(createFhirResourceEntity(input));
@ -2640,6 +2841,9 @@ public class AuthorizationInterceptorR4Test {
@Read(version = true) @Read(version = true)
public CarePlan read(@IdParam IdType theId) { public CarePlan read(@IdParam IdType theId) {
ourHitMethod = true; ourHitMethod = true;
if (ourReturn.isEmpty()) {
throw new ResourceNotFoundException(theId);
}
return (CarePlan) ourReturn.get(0); return (CarePlan) ourReturn.get(0);
} }
@ -2733,11 +2937,14 @@ public class AuthorizationInterceptorR4Test {
@Read(version = true) @Read(version = true)
public Observation read(@IdParam IdType theId) { public Observation read(@IdParam IdType theId) {
ourHitMethod = true; ourHitMethod = true;
if (ourReturn.isEmpty()) {
throw new ResourceNotFoundException(theId);
}
return (Observation) ourReturn.get(0); return (Observation) ourReturn.get(0);
} }
@Search() @Search()
public List<Resource> search() { public List<Resource> search(@OptionalParam(name = "subject") ReferenceParam theSubject) {
ourHitMethod = true; ourHitMethod = true;
return ourReturn; return ourReturn;
} }
@ -2764,6 +2971,7 @@ public class AuthorizationInterceptorR4Test {
} }
@SuppressWarnings("unused")
public static class DummyPatientResourceProvider implements IResourceProvider { public static class DummyPatientResourceProvider implements IResourceProvider {
@Create() @Create()
@ -2861,6 +3069,9 @@ public class AuthorizationInterceptorR4Test {
@Read(version = true) @Read(version = true)
public Patient read(@IdParam IdType theId) { public Patient read(@IdParam IdType theId) {
ourHitMethod = true; ourHitMethod = true;
if (ourReturn.isEmpty()) {
throw new ResourceNotFoundException(theId);
}
return (Patient) ourReturn.get(0); return (Patient) ourReturn.get(0);
} }

View File

@ -1917,7 +1917,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
bundle.getNamedChildren("entry", entries); bundle.getNamedChildren("entry", entries);
Element match = null; Element match = null;
for (Element we : entries) { for (Element we : entries) {
if (we.getChildValue("fullUrl").equals(targetUrl)) { if (targetUrl.equals(we.getChildValue("fullUrl"))) {
Element r = we.getNamedChild("resource"); Element r = we.getNamedChild("resource");
if (version.isEmpty()) { if (version.isEmpty()) {
rule(errors, IssueType.FORBIDDEN, -1, -1, path, match==null, "Multiple matches in bundle for reference " + ref); rule(errors, IssueType.FORBIDDEN, -1, -1, path, match==null, "Multiple matches in bundle for reference " + ref);

View File

@ -317,6 +317,15 @@ public class FhirInstanceValidatorR4Test {
} }
@Test
public void testValidateBundleWithNoFullUrl() throws IOException {
String encoded = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/r4/r4-caredove-bundle.json"));
ValidationResult output = myVal.validateWithResult(encoded);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(44, errors.size());
}
@Test @Test
public void testBase64Valid() { public void testBase64Valid() {
Base64BinaryType value = new Base64BinaryType(new byte[] {2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1}); Base64BinaryType value = new Base64BinaryType(new byte[] {2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1});

View File

@ -0,0 +1,815 @@
{
"resourceType": "Bundle",
"type": "transaction",
"timestamp": "2018-03-09T15:21:51.2112Z",
"entry": [
{
"resource": {
"resourceType": "ServiceRequest",
"id": 1,
"text": {
"status": "generated",
"div": "Human readable HTML narrative of entire ServiceRequest and related resources goes here. For brevity sake, it is omitted here, but can be included in an actual transmission"
},
"status": "active",
"intent": "proposal",
"priority": "routine",
"subject": {
"reference": "Patient/1"
},
"authoredOn": "2018-03-09T15:21:51Z",
"requester": {
"reference": "PractitionerRole/1"
},
"performer": {
"reference": "https://www.caredove.com/FHIR3/HealthcareService/8654"
},
"reasonCode": [
{
"text": "Reason for referral narrative goes here"
}
],
"supportingInfo": [
{
"reference": "DocumentReference/1"
}
],
"note": [
{
"text": "Allergies: Penicillin \nSocial History: History of family conflict \nlow social interaction \nFood Allergies: Peanuts"
}
]
},
"request": {
"method": "POST",
"url": "ServiceRequest"
}
},
{
"resource": {
"resourceType": "Patient",
"id": 1,
"identifier": [
{
"type": {
"coding": [
{
"code": "JHN",
"system": "http://hl7.org/fhir/v2/0203"
}
],
"text": "Ontario PHN"
},
"value": "4455 044 033",
"system": "http://ehealthontario.ca/API/FHIR/NamingSystem/ca-on-patient-hcn",
"extension": [
{
"url": "https://www.caredove.com/FHIR3/StructureDefinition/caredove-healthcardversion",
"valueString": "H"
}
]
}
],
"name": [
{
"given": [
"John",
"Scott"
],
"family": "Smith"
}
],
"telecom": [
{
"system": "phone",
"value": "(555) 111-1111",
"use": "mobile",
"rank": 1
},
{
"system": "phone",
"value": "(555) 222-2222",
"rank": 2
},
{
"system": "email",
"value": "testpatient@caredove.com"
}
],
"gender": "male",
"birthDate": "1928-06-29",
"address": [
{
"use": "home",
"type": "physical",
"line": [
"Unit 2",
"50 Albert St."
],
"city": "Waterloo",
"state": "ON",
"postalCode": "K8N 1N1",
"country": "Can"
}
],
"maritalStatus": {
"coding": [
{
"code": "M",
"display": "Married"
}
],
"text": "Married"
},
"contact": [
{
"relationship": [
{
"text": "Alternate Contact"
}
],
"name": {
"given": [
"Shemergency",
"Scott"
],
"family": "McContact"
},
"telecom": [
{
"system": "phone",
"value": "(555) 111-1111",
"use": "mobile",
"rank": 1
},
{
"system": "phone",
"value": "(555) 222-2222",
"rank": 2
},
{
"system": "email",
"value": "testcontact@caredove.com"
}
],
"address": {
"use": "home",
"type": "physical",
"line": [
"Unit 2",
"50 Albert St."
],
"city": "Waterloo",
"state": "ON",
"postalCode": "32819",
"country": "Can"
},
"gender": "female"
}
],
"communication": [
{
"language": {
"coding": [
{
"code": "en",
"display": "English"
}
],
"text": "English"
},
"preferred": true
}
],
"generalPractitioner": [
{
"reference": "PractitionerRole/2"
}
]
},
"request": {
"method": "POST",
"url": "Patient"
}
},
{
"resource": {
"resourceType": "PractitionerRole",
"id": 1,
"practitioner": {
"reference": "Practitioner/1"
},
"organization": {
"reference": "Organization/1"
},
"location": [
{
"reference": "Location/1"
}
],
"telecom": [
{
"system": "phone",
"value": "(555) 111-1111",
"use": "work"
},
{
"system": "email",
"value": "testsender@caredove.com",
"use": "work"
}
]
},
"request": {
"method": "POST",
"url": "PractitionerRole"
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": 1,
"name": [
{
"given": [
"Requesty"
],
"family": "McSenderson"
}
]
},
"request": {
"method": "POST",
"url": "Practitioner"
}
},
{
"resource": {
"resourceType": "Organization",
"id": 1,
"name": "North Sender Clinic"
},
"request": {
"method": "POST",
"url": "Organization"
}
},
{
"resource": {
"resourceType": "Location",
"id": 1,
"name": "Downtown Sender Hub",
"address": {
"use": "work",
"type": "physical",
"line": [
"Suite 11",
"11 King st. West"
],
"city": "Kitchener",
"state": "ON",
"postalCode": "N2L 1T1",
"country": "Can"
}
},
"request": {
"method": "POST",
"url": "Location"
}
},
{
"resource": {
"resourceType": "PractitionerRole",
"id": 2,
"practitioner": {
"reference": "Practitioner/2"
},
"organization": {
"reference": "Organization/2"
},
"location": [
{
"reference": "Location/2"
}
],
"telecom": [
{
"system": "phone",
"value": "(555) 222-2222",
"use": "work"
},
{
"system": "email",
"value": "familydoc@caredove.com",
"use": "work"
}
]
},
"request": {
"method": "POST",
"url": "PractitionerRole"
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": 2,
"name": [
{
"given": [
"Dr. Prim"
],
"family": "Caredoc"
}
]
},
"request": {
"method": "POST",
"url": "Practitioner"
}
},
{
"resource": {
"resourceType": "Organization",
"id": 2,
"name": "Star Family Health Team"
},
"request": {
"method": "POST",
"url": "Organization"
}
},
{
"resource": {
"resourceType": "DocumentReference",
"id": 1,
"status": "current",
"created": "2018-03-09T15:21:51.2112Z",
"description": "Filename or Document Title goes here",
"content": [
{
"attachment": "NEEDS WORK - ATTACHMENT DATA TYPE",
"format": "NEEDS WORK - FORMAT INFO"
}
]
},
"request": {
"method": "POST",
"url": "Practitioner"
}
},
{
"resource": {
"resourceType": "Location",
"id": 2,
"name": "West Side GP Office",
"address": {
"use": "work",
"type": "physical",
"line": [
"22 Weber st. East"
],
"city": "Kitchener",
"state": "ON",
"postalCode": "N2L 2T2",
"country": "Can"
}
},
"request": {
"method": "POST",
"url": "Location"
}
},
{
"resource": {
"resourceType": "Task",
"id": 1,
"basedOn" : {
"reference" : "ServiceRequest/1"
},
"status" : "requested",
"businessStatus " : {
"text" : "Waiting for preliminary review"
},
"intent" : "proposal",
"code" : {
"text" : "Process Request"
},
"description" : "Process and close this referral request",
"authoredOn" : "2018-03-09T15:21:51Z",
"lastModified" : "2018-03-09T15:21:51Z"
},
"request": {
"method": "POST",
"url": "Task"
}
}
]
}

View File

@ -42,6 +42,30 @@
Spring-data (used by the JPA server) has been upgraded to version 2.0.7 Spring-data (used by the JPA server) has been upgraded to version 2.0.7
(from version 1.11.6). Thanks to Roman Doboni for the pull request! (from version 1.11.6). Thanks to Roman Doboni for the pull request!
</action> </action>
<action type="fix">
A crash in the validator was fixed: Validating a Bundle that did not have fullUrl entries
in a required spot caused a NullPointerException.
</action>
<action type="add">
AuthorizationInterceptor now examines requests more closely in order
to block requests early that are not possibly going to return
allowable results when compartment rules are used. For example,
if an AuthorizationInterceptor is configured to allow only
<![CDATA[<b>read</b>]]>
access to compartment
<![CDATA[<code>Patient/123</code>]]>,
a search for
<![CDATA[<code>Observation?subject=987</code>]]>
will now be blocked before the method handler is called. Previously
the search was performed and the results were examined in order to
determine whether they were all in the appropriate compartment, but
this incurs a performance cost, and means that this search would
successfully return an empty Bundle if no matches were present.
<![CDATA[<br/><br/>]]>
A new setting on AuthorizationInterceptor called
<![CDATA[<code>setFlags(flags)</code>]]>
can be used to maintain the previous behaviour.
</action>
</release> </release>
<release version="3.4.0" date="2018-05-28"> <release version="3.4.0" date="2018-05-28">
<action type="add"> <action type="add">