Further refine #762

This commit is contained in:
James Agnew 2017-11-23 11:05:11 -05:00
parent db1d2d77cd
commit 4887f18bb3
3 changed files with 494 additions and 444 deletions

View File

@ -20,18 +20,6 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.defaultString;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
@ -40,6 +28,21 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import ca.uhn.fhir.util.CoverageIgnore;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.defaultString;
/**
* This class is a base class for interceptors which can be used to
@ -66,9 +69,8 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
/**
* Constructor
*
* @param theDefaultPolicy
* The default policy if no rules apply (must not be null)
*
* @param theDefaultPolicy The default policy if no rules apply (must not be null)
*/
public AuthorizationInterceptor(PolicyEnum theDefaultPolicy) {
this();
@ -76,7 +78,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
}
private void applyRulesAndFailIfDeny(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId,
IBaseResource theOutputResource) {
IBaseResource theOutputResource) {
Verdict decision = applyRulesAndReturnDecision(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
if (decision.getDecision() == PolicyEnum.ALLOW) {
@ -88,7 +90,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
@Override
public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId,
IBaseResource theOutputResource) {
IBaseResource theOutputResource) {
List<IAuthRule> rules = buildRuleList(theRequestDetails);
ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation);
@ -117,9 +119,8 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
* out who the current user is and then using a {@link RuleBuilder} to create
* an appropriate rule chain.
* </p>
*
* @param theRequestDetails
* The individual request currently being applied
*
* @param theRequestDetails The individual request currently being applied
*/
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new ArrayList<IAuthRule>();
@ -127,63 +128,63 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
private OperationExamineDirection determineOperationDirection(RestOperationTypeEnum theOperation, IBaseResource theRequestResource) {
switch (theOperation) {
case ADD_TAGS:
case DELETE_TAGS:
case GET_TAGS:
// These are DSTU1 operations and not relevant
return OperationExamineDirection.NONE;
case ADD_TAGS:
case DELETE_TAGS:
case GET_TAGS:
// These are DSTU1 operations and not relevant
return OperationExamineDirection.NONE;
case EXTENDED_OPERATION_INSTANCE:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:
return OperationExamineDirection.BOTH;
case EXTENDED_OPERATION_INSTANCE:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:
return OperationExamineDirection.BOTH;
case METADATA:
// Security does not apply to these operations
return OperationExamineDirection.IN;
case METADATA:
// Security does not apply to these operations
return OperationExamineDirection.IN;
case DELETE:
// Delete is a special case
return OperationExamineDirection.NONE;
case DELETE:
// Delete is a special case
return OperationExamineDirection.NONE;
case CREATE:
case UPDATE:
case PATCH:
// if (theRequestResource != null) {
// if (theRequestResource.getIdElement() != null) {
// if (theRequestResource.getIdElement().hasIdPart() == false) {
// return OperationExamineDirection.IN_UNCATEGORIZED;
// }
// }
// }
return OperationExamineDirection.IN;
case CREATE:
case UPDATE:
case PATCH:
// if (theRequestResource != null) {
// if (theRequestResource.getIdElement() != null) {
// if (theRequestResource.getIdElement().hasIdPart() == false) {
// return OperationExamineDirection.IN_UNCATEGORIZED;
// }
// }
// }
return OperationExamineDirection.IN;
case META:
case META_ADD:
case META_DELETE:
// meta operations do not apply yet
return OperationExamineDirection.NONE;
case META:
case META_ADD:
case META_DELETE:
// meta operations do not apply yet
return OperationExamineDirection.NONE;
case GET_PAGE:
case HISTORY_INSTANCE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case READ:
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case VREAD:
return OperationExamineDirection.OUT;
case GET_PAGE:
case HISTORY_INSTANCE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case READ:
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case VREAD:
return OperationExamineDirection.OUT;
case TRANSACTION:
return OperationExamineDirection.BOTH;
case TRANSACTION:
return OperationExamineDirection.BOTH;
case VALIDATE:
// Nothing yet
return OperationExamineDirection.NONE;
case VALIDATE:
// Nothing yet
return OperationExamineDirection.NONE;
default:
// Should not happen
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
default:
// Should not happen
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
}
}
@ -195,6 +196,16 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
return myDefaultPolicy;
}
/**
* The default policy if no rules have been found to apply. Default value for this setting is {@link PolicyEnum#DENY}
*
* @param theDefaultPolicy The policy (must not be <code>null</code>)
*/
public void setDefaultPolicy(PolicyEnum theDefaultPolicy) {
Validate.notNull(theDefaultPolicy, "theDefaultPolicy must not be null");
myDefaultPolicy = theDefaultPolicy;
}
/**
* Handle an access control verdict of {@link PolicyEnum#DENY}.
* <p>
@ -221,17 +232,17 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
IIdType inputResourceId = null;
switch (determineOperationDirection(theOperation, theProcessedRequest.getResource())) {
case IN:
case BOTH:
inputResource = theProcessedRequest.getResource();
inputResourceId = theProcessedRequest.getId();
break;
case OUT:
// inputResource = null;
inputResourceId = theProcessedRequest.getId();
break;
case NONE:
return;
case IN:
case BOTH:
inputResource = theProcessedRequest.getResource();
inputResourceId = theProcessedRequest.getId();
break;
case OUT:
// inputResource = null;
inputResourceId = theProcessedRequest.getId();
break;
case NONE:
return;
}
RequestDetails requestDetails = theProcessedRequest.getRequestDetails();
@ -241,43 +252,39 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
switch (determineOperationDirection(theRequestDetails.getRestOperationType(), null)) {
case IN:
case NONE:
return true;
case BOTH:
case OUT:
break;
case IN:
case NONE:
return true;
case BOTH:
case OUT:
break;
}
FhirContext fhirContext = theRequestDetails.getServer().getFhirContext();
List<IBaseResource> resources = Collections.emptyList();
switch (theRequestDetails.getRestOperationType()) {
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_INSTANCE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case TRANSACTION:
case GET_PAGE:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:
case EXTENDED_OPERATION_INSTANCE: {
if (theResponseObject != null) {
if (theResponseObject instanceof IBaseBundle) {
resources = toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext);
} else if (theResponseObject instanceof IBaseParameters) {
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_INSTANCE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case TRANSACTION:
case GET_PAGE:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:
case EXTENDED_OPERATION_INSTANCE: {
if (theResponseObject != null) {
resources = toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext);
}
break;
}
break;
}
default: {
if (theResponseObject != null) {
resources = Collections.singletonList(theResponseObject);
default: {
if (theResponseObject != null) {
resources = Collections.singletonList(theResponseObject);
}
break;
}
break;
}
}
for (IBaseResource nextResponse : resources) {
@ -296,7 +303,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
@CoverageIgnore
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws AuthenticationException {
throws AuthenticationException {
throw failForDstu1();
}
@ -318,33 +325,38 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
handleUserOperation(theRequest, theNewResource, RestOperationTypeEnum.UPDATE);
}
/**
* The default policy if no rules have been found to apply. Default value for this setting is {@link PolicyEnum#DENY}
*
* @param theDefaultPolicy
* The policy (must not be <code>null</code>)
*/
public void setDefaultPolicy(PolicyEnum theDefaultPolicy) {
Validate.notNull(theDefaultPolicy, "theDefaultPolicy must not be null");
myDefaultPolicy = theDefaultPolicy;
}
private List<IBaseResource> toListOfResourcesAndExcludeContainer(IBaseResource theResponseObject, FhirContext fhirContext) {
List<IBaseResource> resources;
resources = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class);
// Exclude the container
if (resources.size() > 0 && resources.get(0) == theResponseObject) {
resources = resources.subList(1, resources.size());
}
return resources;
}
private static UnsupportedOperationException failForDstu1() {
return new UnsupportedOperationException("Use of this interceptor on DSTU1 servers is not supportd");
}
static List<IBaseResource> toListOfResourcesAndExcludeContainer(IBaseResource theResponseObject, FhirContext fhirContext) {
if (theResponseObject == null) {
return Collections.emptyList();
}
List<IBaseResource> retVal;
boolean isContainer = false;
if (theResponseObject instanceof IBaseBundle) {
isContainer = true;
} else if (theResponseObject instanceof IBaseParameters) {
isContainer = true;
}
if (!isContainer) {
return Collections.singletonList(theResponseObject);
}
retVal = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class);
// Exclude the container
if (retVal.size() > 0 && retVal.get(0) == theResponseObject) {
retVal = retVal.subList(1, retVal.size());
}
return retVal;
}
private enum OperationExamineDirection {
BOTH,
IN,

View File

@ -165,13 +165,15 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
}
return verdict;
} else if (theOutputResource != null) {
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
List<IBaseResource> outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(theOutputResource, theRequestDetails.getFhirContext());
Verdict verdict = null;
for (BundleEntryParts nextPart : inputResources) {
if (nextPart.getResource() == null) {
for (IBaseResource nextResource : outputResources) {
if (nextResource == null) {
continue;
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextPart.getResource());
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextResource);
if (newVerdict == null) {
continue;
} else if (verdict == null) {

View File

@ -52,11 +52,11 @@ import static org.junit.Assert.*;
public class AuthorizationInterceptorR4Test {
private static final String ERR403 = "{\"resourceType\":\"OperationOutcome\",\"issue\":[{\"severity\":\"error\",\"code\":\"processing\",\"diagnostics\":\"Access denied by default policy (no applicable rules)\"}]}";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AuthorizationInterceptorR4Test.class);
private static CloseableHttpClient ourClient;
private static String ourConditionalCreateId;
private static FhirContext ourCtx = FhirContext.forR4();
private static boolean ourHitMethod;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AuthorizationInterceptorR4Test.class);
private static int ourPort;
private static List<Resource> ourReturn;
private static Server ourServer;
@ -112,6 +112,77 @@ public class AuthorizationInterceptorR4Test {
return retVal;
}
private Bundle createTransactionWithPlaceholdersRequestBundle() {
// Create a input that will be used as a transaction
Bundle input = new Bundle();
input.setType(Bundle.BundleType.TRANSACTION);
String encounterId = "123-123";
String encounterSystem = "http://our.internal.code.system/encounter";
Encounter encounter = new Encounter();
encounter.addIdentifier(new Identifier().setValue(encounterId)
.setSystem(encounterSystem));
encounter.setStatus(Encounter.EncounterStatus.FINISHED);
Patient p = new Patient()
.addIdentifier(new Identifier().setValue("321-321").setSystem("http://our.internal.code.system/patient"));
p.setId(IdDt.newRandomUuid());
// add patient to input so its created
input.addEntry()
.setFullUrl(p.getId())
.setResource(p)
.getRequest()
.setUrl("Patient")
.setMethod(Bundle.HTTPVerb.POST);
Reference patientRef = new Reference(p.getId());
encounter.setSubject(patientRef);
Condition condition = new Condition()
.setCode(new CodeableConcept().addCoding(
new Coding("http://hl7.org/fhir/icd-10", "S53.40", "FOREARM SPRAIN / STRAIN")))
.setSubject(patientRef);
condition.setId(IdDt.newRandomUuid());
// add condition to input so its created
input.addEntry()
.setFullUrl(condition.getId())
.setResource(condition)
.getRequest()
.setUrl("Condition")
.setMethod(Bundle.HTTPVerb.POST);
Encounter.DiagnosisComponent dc = new Encounter.DiagnosisComponent();
dc.setCondition(new Reference(condition.getId()));
encounter.addDiagnosis(dc);
CodeableConcept reason = new CodeableConcept();
reason.setText("SLIPPED ON FLOOR,PAIN L) ELBOW");
encounter.addReason(reason);
// add encounter to input so its created
input.addEntry()
.setResource(encounter)
.getRequest()
.setUrl("Encounter")
.setIfNoneExist("identifier=" + encounterSystem + "|" + encounterId)
.setMethod(Bundle.HTTPVerb.POST);
return input;
}
private Bundle createTransactionWithPlaceholdersResponseBundle() {
Bundle output = new Bundle();
output.setType(Bundle.BundleType.TRANSACTIONRESPONSE);
output.addEntry()
.setResource(new Patient().setActive(true)) // don't give this an ID
.getResponse().setLocation("/Patient/1");
return output;
}
private String extractResponseAndClose(HttpResponse status) throws IOException {
if (status.getEntity() == null) {
return null;
@ -207,7 +278,7 @@ public class AuthorizationInterceptorR4Test {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder().allow("Rule 1").read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).andThen().denyAll()
.build();
.build();
}
});
@ -264,108 +335,6 @@ public class AuthorizationInterceptorR4Test {
assertEquals(403, status.getStatusLine().getStatusCode());
}
/**
* See #762
*/
//@Test
public void testInstanceRuleOkForResourceWithNoId2() throws IOException {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen()
.allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen()
.denyAll("deny all")
.build();
}
});
// Create a bundle that will be used as a transaction
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
String encounterId = "123-123";
String encounterSystem = "http://our.internal.code.system/encounter";
Encounter encounter = new Encounter();
encounter.addIdentifier(new Identifier().setValue(encounterId)
.setSystem(encounterSystem));
encounter.setStatus(Encounter.EncounterStatus.FINISHED);
Patient p = new Patient()
.addIdentifier(new Identifier().setValue("321-321").setSystem("http://our.internal.code.system/patient"));
p.setId(IdDt.newRandomUuid());
// add patient to bundle so its created
bundle.addEntry()
.setFullUrl(p.getId())
.setResource(p)
.getRequest()
.setUrl("Patient")
.setMethod(Bundle.HTTPVerb.POST);
Reference patientRef = new Reference(p.getId());
encounter.setSubject(patientRef);
Condition condition = new Condition()
.setCode(new CodeableConcept().addCoding(
new Coding("http://hl7.org/fhir/icd-10", "S53.40", "FOREARM SPRAIN / STRAIN")))
.setSubject(patientRef);
condition.setId(IdDt.newRandomUuid());
// add condition to bundle so its created
bundle.addEntry()
.setFullUrl(condition.getId())
.setResource(condition)
.getRequest()
.setUrl("Condition")
.setMethod(Bundle.HTTPVerb.POST);
Encounter.DiagnosisComponent dc = new Encounter.DiagnosisComponent();
dc.setCondition(new Reference(condition.getId()));
encounter.addDiagnosis(dc);
CodeableConcept reason = new CodeableConcept();
reason.setText("SLIPPED ON FLOOR,PAIN L) ELBOW");
encounter.addReason(reason);
// add encounter to bundle so its created
bundle.addEntry()
.setResource(encounter)
.getRequest()
.setUrl("Encounter")
.setIfNoneExist("identifier=" + encounterSystem + "|" + encounterId)
.setMethod(Bundle.HTTPVerb.POST);
Bundle output = new Bundle();
output.setType(Bundle.BundleType.TRANSACTIONRESPONSE);
output.addEntry()
.setResource(new Patient().setActive(true)) // don't give this an ID
.getResponse().setLocation("/Patient/1");
ourReturn = Collections.singletonList((Resource) output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(bundle));
CloseableHttpResponse status = ourClient.execute(httpPost);
String resp = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
ourLog.info(resp);
}
@Test
public void testBatchWhenTransactionReadDenied() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -561,7 +530,7 @@ public class AuthorizationInterceptorR4Test {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder().deny("Rule 1").read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).andThen().allowAll()
.build();
.build();
}
});
@ -623,6 +592,113 @@ public class AuthorizationInterceptorR4Test {
assertTrue(ourHitMethod);
}
/**
* See #762
*/
@Test
public void testTransactionWithPlaceholderIdsResponseUnauthorized() throws IOException {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen()
.allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen()
.denyAll("deny all")
.build();
}
});
Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost);
String resp = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
ourLog.info(resp);
}
/**
* See #762
*/
@Test
public void testTransactionWithPlaceholderIdsResponseAuthorized() throws IOException {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow("read patient").read().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen()
.allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen()
.denyAll("deny all")
.build();
}
});
Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost);
String resp = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
ourLog.info(resp);
}
@Test
public void testInvalidInstanceIds() throws Exception {
try {
new RuleBuilder().allow("Rule 1").write().instance((String) null);
fail();
} catch (NullPointerException e) {
assertEquals("theId must not be null or empty", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance("");
fail();
} catch (IllegalArgumentException e) {
assertEquals("theId must not be null or empty", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance("Observation/");
fail();
} catch (IllegalArgumentException e) {
assertEquals("theId must contain an ID part", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance(new IdType());
fail();
} catch (NullPointerException e) {
assertEquals("theId.getValue() must not be null or empty", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance(new IdType(""));
fail();
} catch (NullPointerException e) {
assertEquals("theId.getValue() must not be null or empty", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance(new IdType("Observation", (String) null));
fail();
} catch (NullPointerException e) {
assertEquals("theId must contain an ID part", e.getMessage());
}
}
@Test
public void testMetadataAllow() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -865,8 +941,8 @@ public class AuthorizationInterceptorR4Test {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onAnyInstance().andThen()
.build();
.allow("RULE 1").operation().named("opName").onAnyInstance().andThen()
.build();
}
});
@ -1272,102 +1348,6 @@ public class AuthorizationInterceptorR4Test {
}
@Test
public void testReadPageRight() 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"))
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String respString;
Bundle respBundle;
ourReturn = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ourReturn.add(createPatient(1));
}
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next"));
// Load next page
ourHitMethod = false;
httpGet = new HttpGet(respBundle.getLink("next").getUrl());
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNull(respBundle.getLink("next"));
}
@Test
public void testReadPageWrong() 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"))
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String respString;
Bundle respBundle;
ourReturn = new ArrayList<>();
for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(1));
}
for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(2));
}
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next"));
// Load next page
ourHitMethod = false;
httpGet = new HttpGet(respBundle.getLink("next").getUrl());
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testReadByCompartmentWrong() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -1436,6 +1416,147 @@ public class AuthorizationInterceptorR4Test {
}
@Test
public void testReadByInstance() throws Exception {
ourConditionalCreateId = "1";
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().instance("Observation/900").andThen()
.allow("Rule 1").read().instance("901").andThen()
.build();
}
});
HttpResponse status;
String response;
HttpGet httpGet;
ourReturn = Collections.singletonList(createObservation(900, "Patient/1"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/900");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createPatient(901));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/901");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createPatient(1));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=json");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertEquals(ERR403, response);
assertFalse(ourHitMethod);
}
@Test
public void testReadPageRight() 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"))
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String respString;
Bundle respBundle;
ourReturn = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ourReturn.add(createPatient(1));
}
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next"));
// Load next page
ourHitMethod = false;
httpGet = new HttpGet(respBundle.getLink("next").getUrl());
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNull(respBundle.getLink("next"));
}
@Test
public void testReadPageWrong() 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"))
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String respString;
Bundle respBundle;
ourReturn = new ArrayList<>();
for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(1));
}
for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(2));
}
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next"));
// Load next page
ourHitMethod = false;
httpGet = new HttpGet(respBundle.getLink("next").getUrl());
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testTransactionWriteGood() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -1818,82 +1939,6 @@ public class AuthorizationInterceptorR4Test {
}
@Test
public void testInvalidInstanceIds() throws Exception {
try {
new RuleBuilder().allow("Rule 1").write().instance((String) null);
fail();
} catch (NullPointerException e) {
assertEquals("theId must not be null or empty", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance("");
fail();
} catch (IllegalArgumentException e) {
assertEquals("theId must not be null or empty", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance("Observation/");
fail();
} catch (IllegalArgumentException e) {
assertEquals("theId must contain an ID part", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance(new IdType());
fail();
} catch (NullPointerException e) {
assertEquals("theId.getValue() must not be null or empty", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance(new IdType(""));
fail();
} catch (NullPointerException e) {
assertEquals("theId.getValue() must not be null or empty", e.getMessage());
}
try {
new RuleBuilder().allow("Rule 1").write().instance(new IdType("Observation", (String) null));
fail();
} catch (NullPointerException e) {
assertEquals("theId must contain an ID part", e.getMessage());
}
}
@Test
public void testWritePatchByInstance() throws Exception {
ourConditionalCreateId = "1";
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").write().instance("Patient/900").andThen()
.build();
}
});
HttpEntityEnclosingRequestBase httpPost;
HttpResponse status;
String response;
String input = "[ { \"op\": \"replace\", \"path\": \"/gender\", \"value\": \"male\" } ]";
ourHitMethod = false;
httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/900");
httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json")));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
assertEquals(204, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/999");
httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json")));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testWriteByInstance() throws Exception {
ourConditionalCreateId = "1";
@ -1949,48 +1994,39 @@ public class AuthorizationInterceptorR4Test {
}
@Test
public void testReadByInstance() throws Exception {
public void testWritePatchByInstance() throws Exception {
ourConditionalCreateId = "1";
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().instance("Observation/900").andThen()
.allow("Rule 1").read().instance("901").andThen()
.allow("Rule 1").write().instance("Patient/900").andThen()
.build();
}
});
HttpEntityEnclosingRequestBase httpPost;
HttpResponse status;
String response;
HttpGet httpGet;
ourReturn = Collections.singletonList(createObservation(900, "Patient/1"));
String input = "[ { \"op\": \"replace\", \"path\": \"/gender\", \"value\": \"male\" } ]";
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/900");
status = ourClient.execute(httpGet);
httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/900");
httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json")));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(204, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createPatient(901));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/901");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createPatient(1));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=json");
status = ourClient.execute(httpGet);
httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/999");
httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json")));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertEquals(ERR403, response);
assertFalse(ourHitMethod);
}
@AfterClass
@ -2220,6 +2256,14 @@ public class AuthorizationInterceptorR4Test {
return (Parameters) new Parameters().setId("1");
}
@Patch()
public MethodOutcome patch(@IdParam IdType theId, @ResourceParam String theResource, PatchTypeEnum thePatchType) {
ourHitMethod = true;
MethodOutcome retVal = new MethodOutcome();
return retVal;
}
@Read(version = true)
public Patient read(@IdParam IdType theId) {
ourHitMethod = true;
@ -2252,17 +2296,9 @@ public class AuthorizationInterceptorR4Test {
return retVal;
}
@Patch()
public MethodOutcome patch(@IdParam IdType theId, @ResourceParam String theResource, PatchTypeEnum thePatchType) {
ourHitMethod = true;
MethodOutcome retVal = new MethodOutcome();
return retVal;
}
@Validate
public MethodOutcome validate(@ResourceParam Patient theResource, @IdParam IdType theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding,
@Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile, RequestDetails theRequestDetails) {
@Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile, RequestDetails theRequestDetails) {
ourHitMethod = true;
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setDiagnostics("OK");
@ -2271,7 +2307,7 @@ public class AuthorizationInterceptorR4Test {
@Validate
public MethodOutcome validate(@ResourceParam Patient theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
ourHitMethod = true;
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setDiagnostics("OK");