diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java index 2d88cda51d8..908e9917af7 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java @@ -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 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. *

- * - * @param theRequestDetails - * The individual request currently being applied + * + * @param theRequestDetails The individual request currently being applied */ public List buildRuleList(RequestDetails theRequestDetails) { return new ArrayList(); @@ -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 null) + */ + public void setDefaultPolicy(PolicyEnum theDefaultPolicy) { + Validate.notNull(theDefaultPolicy, "theDefaultPolicy must not be null"); + myDefaultPolicy = theDefaultPolicy; + } + /** * Handle an access control verdict of {@link PolicyEnum#DENY}. *

@@ -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 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 null) - */ - public void setDefaultPolicy(PolicyEnum theDefaultPolicy) { - Validate.notNull(theDefaultPolicy, "theDefaultPolicy must not be null"); - myDefaultPolicy = theDefaultPolicy; - } - - private List toListOfResourcesAndExcludeContainer(IBaseResource theResponseObject, FhirContext fhirContext) { - List 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 toListOfResourcesAndExcludeContainer(IBaseResource theResponseObject, FhirContext fhirContext) { + if (theResponseObject == null) { + return Collections.emptyList(); + } + + List 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, diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java index a8b8e113053..1d9fb8c838a 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java @@ -165,13 +165,15 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ { } return verdict; } else if (theOutputResource != null) { - List inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource); + + List 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) { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java index 8e633ee2417..86944b35191 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuthorizationInterceptorR4Test.java @@ -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 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 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 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 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 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 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 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 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 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 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 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 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 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 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");