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; public class AuthorizationInterceptors { public class PatientResourceProvider implements IResourceProvider { @Override public Class<? extends IBaseResource> getResourceType() { return Patient.class; } public MethodOutcome create(@ResourceParam Patient thePatient, RequestDetails theRequestDetails) { return new MethodOutcome(); // populate this } } //START SNIPPET: patientAndAdmin public class PatientAndAdminAuthorizationInterceptor extends AuthorizationInterceptor { @Override public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { // Process authorization header - The following is a fake // implementation. Obviously we'd want something more real // for a production scenario. // // In this basic example we have two hardcoded bearer tokens, // one which is for a user that has access to one patient, and // another that has full access. IdDt userIdPatientId = null; boolean userIsAdmin = false; String authHeader = theRequestDetails.getHeader("Authorization"); if ("Bearer dfw98h38r".equals(authHeader)) { // This user has access only to Patient/1 resources userIdPatientId = new IdDt("Patient", 1L); } else if ("Bearer 39ff939jgg".equals(authHeader)) { // This user has access to everything userIsAdmin = true; } else { // Throw an HTTP 401 throw new AuthenticationException("Missing or invalid Authorization header value"); } // If the user is a specific patient, we create the following rule chain: // Allow the user to read anything in their own patient compartment // Allow the user to write anything in their own patient compartment // If a client request doesn't pass either of the above, deny it if (userIdPatientId != null) { return new RuleBuilder() .allow().read().allResources().inCompartment("Patient", userIdPatientId).andThen() .allow().write().allResources().inCompartment("Patient", userIdPatientId).andThen() .denyAll() .build(); } // If the user is an admin, allow everything if (userIsAdmin) { return new RuleBuilder() .allowAll() .build(); } // By default, deny everything. This should never get hit, but it's // good to be defensive return new RuleBuilder() .denyAll() .build(); } } //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 }