diff --git a/examples/src/main/java/example/AuthorizationInterceptors.java b/examples/src/main/java/example/AuthorizationInterceptors.java index 0bc661c5bd03..3f84c3531dbe 100644 --- a/examples/src/main/java/example/AuthorizationInterceptors.java +++ b/examples/src/main/java/example/AuthorizationInterceptors.java @@ -2,33 +2,89 @@ 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.ResourceParam; +import ca.uhn.fhir.rest.api.MethodOutcome; 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.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 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 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"); - /* - * Process authorization header - The following is a fake - * implementation. Obviously we'd want something more real - * for a production scenario. - */ + 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 authorization header was determined to be - Long callerIsPatientId = null; + // 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() - .deny("Rule 1").read().resourcesOfType(Patient.class).withAnyId().andThen() - .allowAll("Default Rule") + .denyAll() .build(); } } + //END SNIPPET: patientAndAdmin } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java index 922adb5b1e1e..89ab445fd656 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java @@ -49,6 +49,11 @@ * This class is a base class for interceptors which can be used to * inspect requests and responses to determine whether the calling user * has permission to perform the given action. + *

+ * See the HAPI FHIR + * Documentation on Server Security + * for information on how to use this interceptor. + *

*/ public class AuthorizationInterceptor extends InterceptorAdapter implements IServerOperationInterceptor, IRuleApplier { diff --git a/src/site/site.xml b/src/site/site.xml index 1cecd14f04c7..3312b0cf69b0 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -97,8 +97,9 @@ - + + diff --git a/src/site/xdoc/doc_jpa.xml b/src/site/xdoc/doc_jpa.xml index b339c3a8be7a..179d82cb449c 100644 --- a/src/site/xdoc/doc_jpa.xml +++ b/src/site/xdoc/doc_jpa.xml @@ -143,7 +143,7 @@ $ mvn install]]>

diff --git a/src/site/xdoc/doc_rest_server_security.xml b/src/site/xdoc/doc_rest_server_security.xml index 8b41f88d40fc..e201e5d4cbc4 100644 --- a/src/site/xdoc/doc_rest_server_security.xml +++ b/src/site/xdoc/doc_rest_server_security.xml @@ -81,7 +81,9 @@

AuthorizationInterceptor is a new feature in HAPI FHIR, and has not yet been heavily tested. Use with caution, and do lots of testing! We welcome - feedback and suggestions on this feature. + feedback and suggestions on this feature. In addition, this documentation is + not yet complete. More examples and details will be added soon! Please get in + touch if you'd like to help test, have suggestions, etc.

@@ -96,6 +98,45 @@ might be detemrined to belong to an administrator user, and could be declared to be allowed to do anything.

+ +

+ The AuthorizationInterceptor is used by subclassing it and then registering your + subclass with the RestfulServer. The following example shows a subclassed + interceptor implementing some basic rules: +

+ + + + + + + + +

+ The AuthorizationInterceptor works by examining the client request + in order to determine whether "write" operations are legal, and looks at + the response from the server in order to determine whether "read" operations + are legal. +

+

+ This approach has limitations however: If a request has a conditional operation, + such as a delete operation which uses a search URL, or a create operation which + uses an If-None-Exist header, the interceptor will not know the + actual target until the server actually processes the request. +

+

+ For better security, individual resource providers should notify interceptors + about their actual targets in the event of any "write" operations (create, + operations embedded in transactions, etc.) +

+

+ The mechanism for doing this isn't yet fully documented, this will be improved + over the next release cycle (post 1.5). Please get in touch on our google group + if you want to help! +

+ +
+ diff --git a/src/site/xdoc/docindex.xml b/src/site/xdoc/docindex.xml index 3a14d1e6856c..de0984c071a2 100644 --- a/src/site/xdoc/docindex.xml +++ b/src/site/xdoc/docindex.xml @@ -44,8 +44,9 @@
  • Using RESTful Server
  • RESTful Operations
  • Narrative Generator
  • -
  • CORS Support
  • Interceptors (server)
  • +
  • Security
  • +
  • CORS Support
  • Web Testing UI
  • JAX-RS Support
  • diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index 3de43b738c84..cdb1299fd85f 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -93,6 +93,12 @@ Server Security Interceptor has been added. +
  • + The JPA server has been enhanced so that search results are now paged into + the database instead of simply to memory. This makes the server much more + scalable to supporting larger result sets, larger volumes of queries, and + operation across multiple nodes in a cluster. +
  • - James Agnew