Add documentation around security interceptor

This commit is contained in:
jamesagnew 2016-06-25 11:27:55 -04:00
parent 556058a7b7
commit fae4344c36
4 changed files with 121 additions and 3 deletions

View File

@ -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
}

View File

@ -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;
}

View File

@ -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);

View File

@ -167,6 +167,36 @@
<img src="./images/hapi_authorizationinterceptor_write_normal.svg" alt="Write Authorization"/>
</subsection>
<subsection name="Authorizing Sub-Operations">
<p>
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 <code>conditional update</code> operation, the server might not know
which resource is actually being updated until the server code
is executed.
</p>
<p>
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.
</p>
<p>
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.
</p>
<macro name="snippet">
<param name="id" value="conditionalUpdate" />
<param name="file" value="examples/src/main/java/example/AuthorizationInterceptors.java" />
</macro>
</subsection>
</section>