diff --git a/examples/src/main/java/example/AuthorizationInterceptors.java b/examples/src/main/java/example/AuthorizationInterceptors.java index 3f84c3531db..d9ead4acb0a 100644 --- a/examples/src/main/java/example/AuthorizationInterceptors.java +++ b/examples/src/main/java/example/AuthorizationInterceptors.java @@ -1,16 +1,24 @@ package example; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + import java.util.List; import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor; import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule; import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder; @@ -87,4 +95,43 @@ public class AuthorizationInterceptors { } //END SNIPPET: patientAndAdmin + + //START SNIPPET: conditionalUpdate + @Update() + public MethodOutcome update( + @IdParam IdDt theId, + @ResourceParam Patient theResource, + @ConditionalUrlParam String theConditionalUrl, + RequestDetails theRequestDetails) { + + // If we're processing a conditional URL... + if (isNotBlank(theConditionalUrl)) { + + // Pretend we've done the conditional processing. Now let's + // notify the interceptors that an update has been performed + // and supply the actual ID that's being updated + IdDt actual = new IdDt("Patient", "1123"); + + // There are a number of possible constructors for ActionRequestDetails. + // You should supply as much detail about the sub-operation as possible + IServerInterceptor.ActionRequestDetails subRequest = + new IServerInterceptor.ActionRequestDetails(theRequestDetails, actual); + + // Notify the interceptors + subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.UPDATE); + } + + // In a real server, perhaps we would process the conditional + // request differently and follow a separate path. Either way, + // let's pretend there is some storage code here. + + theResource.setId(theId.withVersion("2")); + MethodOutcome retVal = new MethodOutcome(); + retVal.setCreated(true); + retVal.setResource(theResource); + return retVal; + } + //END SNIPPET: conditionalUpdate + + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java index 028819d9a2a..2d60a245b2d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java @@ -404,6 +404,16 @@ public interface IServerInterceptor { this(theRequestDetails, theRequestDetails.getServer().getFhirContext(), theResourceType, theId); } + /** + * Constructor + * + * @param theRequestDetails The request details to wrap + * @param theId The ID of the resource being created (note that the ID should have the resource type populated) + */ + public ActionRequestDetails(RequestDetails theRequestDetails, IIdType theId) { + this(theRequestDetails, theId.getResourceType(), theId); + } + public FhirContext getContext() { return myContext; } diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorDstu2Test.java index 643b8ec11cb..bb113db9453 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorDstu2Test.java @@ -59,6 +59,7 @@ import ca.uhn.fhir.rest.annotation.TransactionParam; import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.method.IRequestOperationCallback; import ca.uhn.fhir.rest.method.RequestDetails; @@ -793,6 +794,7 @@ public class AuthorizationInterceptorDstu2Test { //@formatter:off return new RuleBuilder() .allow("Rule 1").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1")).andThen() + .allow("Rule 1b").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1123")).andThen() .allow("Rule 2").write().resourcesOfType(Observation.class).inCompartment("Patient", new IdDt("Patient/1")) .build(); //@formatter:on @@ -811,6 +813,17 @@ public class AuthorizationInterceptorDstu2Test { assertEquals(403, status.getStatusLine().getStatusCode()); assertFalse(ourHitMethod); + // Conditional + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); + httpPost.addHeader("If-None-Exist", "Patient?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createPatient(null))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(ERR403, response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + ourHitMethod = false; httpPost = new HttpPost("http://localhost:" + ourPort + "/Observation"); httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/2"))); @@ -897,6 +910,16 @@ public class AuthorizationInterceptorDstu2Test { assertEquals(201, status.getStatusLine().getStatusCode()); assertTrue(ourHitMethod); + // Conditional + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createPatient(1))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(ERR403, response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + ourHitMethod = false; httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation/10"); httpPost.setEntity(createFhirResourceEntity(createObservation(10, "Patient/1"))); @@ -1016,8 +1039,9 @@ public class AuthorizationInterceptorDstu2Test { public MethodOutcome create(@ResourceParam Patient theResource, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) { if (isNotBlank(theConditionalUrl)) { - ActionRequestDetails subRequest = new ActionRequestDetails(theRequestDetails); - + IdDt actual = new IdDt("Patient", "1123"); + ActionRequestDetails subRequest = new ActionRequestDetails(theRequestDetails, actual); + subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.CREATE); } ourHitMethod = true; @@ -1075,8 +1099,15 @@ public class AuthorizationInterceptorDstu2Test { } @Update() - public MethodOutcome update(@IdParam IdDt theId, @ResourceParam Patient theResource) { + public MethodOutcome update(@IdParam IdDt theId, @ResourceParam Patient theResource, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) { ourHitMethod = true; + + if (isNotBlank(theConditionalUrl)) { + IdDt actual = new IdDt("Patient", "1123"); + ActionRequestDetails subRequest = new ActionRequestDetails(theRequestDetails, actual); + subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.UPDATE); + } + theResource.setId(theId.withVersion("2")); MethodOutcome retVal = new MethodOutcome(); retVal.setCreated(true); diff --git a/src/site/xdoc/doc_rest_server_security.xml b/src/site/xdoc/doc_rest_server_security.xml index a39aae6fbde..ca08cc1cdc8 100644 --- a/src/site/xdoc/doc_rest_server_security.xml +++ b/src/site/xdoc/doc_rest_server_security.xml @@ -167,6 +167,36 @@ Write Authorization + + + +

+ There are a number of situations where the REST framework doesn't + actually know exactly what operation is going to be performed by + the implementing server code. For example, if your server implements + a conditional update operation, the server might not know + which resource is actually being updated until the server code + is executed. +

+

+ Because client code is actually determining which resources are + being modified, the server can not automatically apply security + rules against these modifications without being provided hints + from client code. +

+

+ In this type of situation, it is important to manually + notify the interceptor chain about the "sub-operation" being performed. + The following snippet shows how to notify interceptors about + a conditional create. +

+ + + + + + +