Add documentation around security interceptor
This commit is contained in:
parent
556058a7b7
commit
fae4344c36
|
@ -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
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue