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:
parent
903df68d2a
commit
cc0e836680
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -333,7 +371,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
||||||
if (theResponseObject == null) {
|
if (theResponseObject == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IBaseResource> retVal;
|
List<IBaseResource> retVal;
|
||||||
|
|
||||||
boolean isContainer = false;
|
boolean isContainer = false;
|
||||||
|
@ -342,11 +380,11 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
||||||
} else if (theResponseObject instanceof IBaseParameters) {
|
} else if (theResponseObject instanceof IBaseParameters) {
|
||||||
isContainer = true;
|
isContainer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isContainer) {
|
if (!isContainer) {
|
||||||
return Collections.singletonList(theResponseObject);
|
return Collections.singletonList(theResponseObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
retVal = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class);
|
retVal = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class);
|
||||||
|
|
||||||
// Exclude the container
|
// Exclude the container
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -11,9 +34,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -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,232 +79,327 @@ 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) {
|
||||||
switch (theOperation) {
|
|
||||||
case READ:
|
|
||||||
case VREAD:
|
|
||||||
appliesToResourceId = theInputResourceId;
|
|
||||||
appliesToResourceType = theInputResourceId.getResourceType();
|
|
||||||
break;
|
|
||||||
case SEARCH_SYSTEM:
|
|
||||||
case SEARCH_TYPE:
|
|
||||||
case HISTORY_INSTANCE:
|
|
||||||
case HISTORY_SYSTEM:
|
|
||||||
case HISTORY_TYPE:
|
|
||||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Verdict(PolicyEnum.ALLOW, this);
|
|
||||||
default:
|
switch (theOperation) {
|
||||||
return null;
|
case READ:
|
||||||
|
case VREAD:
|
||||||
|
appliesToResourceId = theInputResourceId;
|
||||||
|
appliesToResourceType = theInputResourceId.getResourceType();
|
||||||
|
break;
|
||||||
|
case SEARCH_SYSTEM:
|
||||||
|
case HISTORY_SYSTEM:
|
||||||
|
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||||
|
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:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appliesToResource = theOutputResource;
|
||||||
|
if (theOutputResource != null) {
|
||||||
|
appliesToResourceId = theOutputResource.getIdElement();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
appliesToResource = theOutputResource;
|
|
||||||
if (theOutputResource != null) {
|
|
||||||
appliesToResourceId = theOutputResource.getIdElement();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WRITE:
|
|
||||||
if (theInputResource == null && theInputResourceId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
switch (theOperation) {
|
|
||||||
case CREATE:
|
|
||||||
case UPDATE:
|
|
||||||
case ADD_TAGS:
|
|
||||||
case DELETE_TAGS:
|
|
||||||
case META_ADD:
|
|
||||||
case META_DELETE:
|
|
||||||
case PATCH:
|
|
||||||
appliesToResource = theInputResource;
|
|
||||||
appliesToResourceId = theInputResourceId;
|
|
||||||
break;
|
break;
|
||||||
default:
|
case WRITE:
|
||||||
return null;
|
if (theInputResource == null && theInputResourceId == null) {
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DELETE:
|
|
||||||
if (theOperation == RestOperationTypeEnum.DELETE) {
|
|
||||||
if (theInputResource == null) {
|
|
||||||
return newVerdict();
|
|
||||||
}
|
|
||||||
appliesToResource = theInputResource;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BATCH:
|
|
||||||
case TRANSACTION:
|
|
||||||
if (!(theOperation == RestOperationTypeEnum.TRANSACTION)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
|
|
||||||
if (getMode() == PolicyEnum.DENY) {
|
|
||||||
return new Verdict(PolicyEnum.DENY, this);
|
|
||||||
}
|
|
||||||
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
|
|
||||||
Verdict verdict = null;
|
|
||||||
for (BundleEntryParts nextPart : inputResources) {
|
|
||||||
|
|
||||||
IBaseResource inputResource = nextPart.getResource();
|
|
||||||
RestOperationTypeEnum operation = null;
|
|
||||||
if (nextPart.getRequestType() == RequestTypeEnum.GET) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (nextPart.getRequestType() == RequestTypeEnum.POST) {
|
|
||||||
operation = RestOperationTypeEnum.CREATE;
|
|
||||||
} else if (nextPart.getRequestType() == RequestTypeEnum.PUT) {
|
|
||||||
operation = RestOperationTypeEnum.UPDATE;
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException("Can not handle transaction with operation of type " + nextPart.getRequestType());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is basically just being conservative - Be careful of transactions containing
|
|
||||||
* nested operations and nested transactions. We block the by default. At some point
|
|
||||||
* it would be nice to be more nuanced here.
|
|
||||||
*/
|
|
||||||
RuntimeResourceDefinition resourceDef = ctx.getResourceDefinition(nextPart.getResource());
|
|
||||||
if ("Parameters".equals(resourceDef.getName()) || "Bundle".equals(resourceDef.getName())) {
|
|
||||||
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null, null);
|
|
||||||
if (newVerdict == null) {
|
|
||||||
continue;
|
|
||||||
} else if (verdict == null) {
|
|
||||||
verdict = newVerdict;
|
|
||||||
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
|
|
||||||
verdict = newVerdict;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return verdict;
|
|
||||||
} else if (theOutputResource != null) {
|
|
||||||
|
|
||||||
List<IBaseResource> outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(theOutputResource, theRequestDetails.getFhirContext());
|
|
||||||
|
|
||||||
Verdict verdict = null;
|
|
||||||
for (IBaseResource nextResource : outputResources) {
|
|
||||||
if (nextResource == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextResource);
|
|
||||||
if (newVerdict == null) {
|
|
||||||
continue;
|
|
||||||
} else if (verdict == null) {
|
|
||||||
verdict = newVerdict;
|
|
||||||
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
|
|
||||||
verdict = newVerdict;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return verdict;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
case ALLOW_ALL:
|
|
||||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Verdict(PolicyEnum.ALLOW, this);
|
|
||||||
case DENY_ALL:
|
|
||||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Verdict(PolicyEnum.DENY, this);
|
|
||||||
case METADATA:
|
|
||||||
if (theOperation == RestOperationTypeEnum.METADATA) {
|
|
||||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return newVerdict();
|
switch (theOperation) {
|
||||||
}
|
case CREATE:
|
||||||
return null;
|
case UPDATE:
|
||||||
default:
|
case ADD_TAGS:
|
||||||
// Should not happen
|
case DELETE_TAGS:
|
||||||
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
|
case META_ADD:
|
||||||
}
|
case META_DELETE:
|
||||||
|
case PATCH:
|
||||||
|
appliesToResource = theInputResource;
|
||||||
|
appliesToResourceId = theInputResourceId;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DELETE:
|
||||||
|
if (theOperation == RestOperationTypeEnum.DELETE) {
|
||||||
|
if (theInputResource == null) {
|
||||||
|
return newVerdict();
|
||||||
|
}
|
||||||
|
appliesToResource = theInputResource;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BATCH:
|
||||||
|
case TRANSACTION:
|
||||||
|
if (!(theOperation == RestOperationTypeEnum.TRANSACTION)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
|
||||||
|
if (getMode() == PolicyEnum.DENY) {
|
||||||
|
return new Verdict(PolicyEnum.DENY, this);
|
||||||
|
}
|
||||||
|
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
|
||||||
|
Verdict verdict = null;
|
||||||
|
for (BundleEntryParts nextPart : inputResources) {
|
||||||
|
|
||||||
switch (myAppliesTo) {
|
IBaseResource inputResource = nextPart.getResource();
|
||||||
case INSTANCES:
|
RestOperationTypeEnum operation = null;
|
||||||
if (appliesToResourceId != null) {
|
if (nextPart.getRequestType() == RequestTypeEnum.GET) {
|
||||||
for (IIdType next : myAppliesToInstances) {
|
|
||||||
if (isNotBlank(next.getResourceType())) {
|
|
||||||
if (!next.getResourceType().equals(appliesToResourceId.getResourceType())) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (nextPart.getRequestType() == RequestTypeEnum.POST) {
|
||||||
|
operation = RestOperationTypeEnum.CREATE;
|
||||||
|
} else if (nextPart.getRequestType() == RequestTypeEnum.PUT) {
|
||||||
|
operation = RestOperationTypeEnum.UPDATE;
|
||||||
|
} else {
|
||||||
|
throw new InvalidRequestException("Can not handle transaction with operation of type " + nextPart.getRequestType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is basically just being conservative - Be careful of transactions containing
|
||||||
|
* nested operations and nested transactions. We block the by default. At some point
|
||||||
|
* it would be nice to be more nuanced here.
|
||||||
|
*/
|
||||||
|
RuntimeResourceDefinition resourceDef = ctx.getResourceDefinition(nextPart.getResource());
|
||||||
|
if ("Parameters".equals(resourceDef.getName()) || "Bundle".equals(resourceDef.getName())) {
|
||||||
|
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null, null);
|
||||||
|
if (newVerdict == null) {
|
||||||
|
continue;
|
||||||
|
} else if (verdict == null) {
|
||||||
|
verdict = newVerdict;
|
||||||
|
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
|
||||||
|
verdict = newVerdict;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!next.getIdPart().equals(appliesToResourceId.getIdPart())) {
|
return verdict;
|
||||||
continue;
|
} else if (theOutputResource != null) {
|
||||||
|
|
||||||
|
List<IBaseResource> outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(theOutputResource, theRequestDetails.getFhirContext());
|
||||||
|
|
||||||
|
Verdict verdict = null;
|
||||||
|
for (IBaseResource nextResource : outputResources) {
|
||||||
|
if (nextResource == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextResource);
|
||||||
|
if (newVerdict == null) {
|
||||||
|
continue;
|
||||||
|
} else if (verdict == null) {
|
||||||
|
verdict = newVerdict;
|
||||||
|
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
|
||||||
|
verdict = newVerdict;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
return verdict;
|
||||||
return null;
|
} else {
|
||||||
}
|
return null;
|
||||||
return newVerdict();
|
|
||||||
}
|
}
|
||||||
}
|
case ALLOW_ALL:
|
||||||
return null;
|
|
||||||
case ALL_RESOURCES:
|
|
||||||
if (appliesToResourceType != null) {
|
|
||||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Verdict(PolicyEnum.ALLOW, this);
|
return new Verdict(PolicyEnum.ALLOW, this);
|
||||||
}
|
case DENY_ALL:
|
||||||
break;
|
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||||
case TYPES:
|
|
||||||
if (appliesToResource != null) {
|
|
||||||
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
return new Verdict(PolicyEnum.DENY, this);
|
||||||
if (appliesToResourceId != null && appliesToResourceId.hasResourceType()) {
|
case METADATA:
|
||||||
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
|
if (theOperation == RestOperationTypeEnum.METADATA) {
|
||||||
if (myAppliesToTypes.contains(type) == false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (appliesToResourceType != null) {
|
|
||||||
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceType).getImplementingClass();
|
|
||||||
if (myAppliesToTypes.contains(type)) {
|
|
||||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Verdict(PolicyEnum.ALLOW, this);
|
return newVerdict();
|
||||||
}
|
}
|
||||||
}
|
return null;
|
||||||
break;
|
default:
|
||||||
default:
|
// Should not happen
|
||||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (myAppliesTo) {
|
||||||
|
case INSTANCES:
|
||||||
|
if (appliesToResourceId != null) {
|
||||||
|
for (IIdType next : myAppliesToInstances) {
|
||||||
|
if (isNotBlank(next.getResourceType())) {
|
||||||
|
if (!next.getResourceType().equals(appliesToResourceId.getResourceType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!next.getIdPart().equals(appliesToResourceId.getIdPart())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return newVerdict();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case ALL_RESOURCES:
|
||||||
|
if (appliesToResourceType != null) {
|
||||||
|
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||||
|
return new Verdict(PolicyEnum.ALLOW, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPES:
|
||||||
|
if (appliesToResource != null) {
|
||||||
|
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||||
|
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||||
|
if (appliesToResourceId != null && appliesToResourceId.hasResourceType()) {
|
||||||
|
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
|
||||||
|
if (myAppliesToTypes.contains(type) == false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
if (appliesToResourceType != null) {
|
||||||
|
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceType).getImplementingClass();
|
||||||
|
if (myAppliesToTypes.contains(type)) {
|
||||||
|
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||||
|
return new Verdict(PolicyEnum.ALLOW, this);
|
||||||
|
} 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) {
|
switch (myClassifierType) {
|
||||||
case ANY_ID:
|
case ANY_ID:
|
||||||
break;
|
break;
|
||||||
case IN_COMPARTMENT:
|
case IN_COMPARTMENT:
|
||||||
FhirTerser t = ctx.newTerser();
|
FhirTerser t = ctx.newTerser();
|
||||||
boolean foundMatch = false;
|
boolean foundMatch = false;
|
||||||
for (IIdType next : myClassifierCompartmentOwners) {
|
for (IIdType next : myClassifierCompartmentOwners) {
|
||||||
if (appliesToResource != null) {
|
if (appliesToResource != null) {
|
||||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesToResource, next)) {
|
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesToResource, next)) {
|
||||||
foundMatch = true;
|
foundMatch = true;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (appliesToResourceId != null && appliesToResourceId.hasResourceType() && appliesToResourceId.hasIdPart()) {
|
||||||
|
if (appliesToResourceId.toUnqualifiedVersionless().getValue().equals(next.toUnqualifiedVersionless().getValue())) {
|
||||||
|
foundMatch = true;
|
||||||
|
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 (appliesToResourceId != null && appliesToResourceId.hasResourceType() && appliesToResourceId.hasIdPart()) {
|
if (!foundMatch) {
|
||||||
if (appliesToResourceId.toUnqualifiedVersionless().getValue().equals(next.toUnqualifiedVersionless().getValue())) {
|
return null;
|
||||||
foundMatch = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
if (!foundMatch) {
|
default:
|
||||||
return null;
|
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||||
|
@ -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) {
|
||||||
|
@ -334,23 +425,23 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
IBaseBundle request = (IBaseBundle) theInputResource;
|
IBaseBundle request = (IBaseBundle) theInputResource;
|
||||||
String bundleType = BundleUtil.getBundleType(theContext, request);
|
String bundleType = BundleUtil.getBundleType(theContext, request);
|
||||||
switch (theOp) {
|
switch (theOp) {
|
||||||
case TRANSACTION:
|
case TRANSACTION:
|
||||||
return "transaction".equals(bundleType);
|
return "transaction".equals(bundleType);
|
||||||
case BATCH:
|
case BATCH:
|
||||||
return "batch".equals(bundleType);
|
return "batch".equals(bundleType);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,8 +1178,8 @@ 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,8 +1227,8 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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});
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in New Issue