Add bulk export authorization layer (#2712)
* Add bulk export authorization layer * Add docs * Version bump * Authorize any * Add bulk export - all * Address lgtm issue
This commit is contained in:
parent
4f1f09abb1
commit
dc627dc019
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -1038,7 +1038,7 @@ public enum Pointcut implements IPointcut {
|
|||
*/
|
||||
STORAGE_INITIATE_BULK_EXPORT(
|
||||
void.class,
|
||||
"ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions",
|
||||
"ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions",
|
||||
"ca.uhn.fhir.rest.api.server.RequestDetails",
|
||||
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
|
||||
),
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-bom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>HAPI FHIR BOM</name>
|
||||
|
||||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-cli</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import ca.uhn.fhir.rest.server.IResourceProvider;
|
|||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.*;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
|
@ -192,6 +193,19 @@ public class AuthorizationInterceptors {
|
|||
};
|
||||
//END SNIPPET: patchAll
|
||||
|
||||
|
||||
//START SNIPPET: bulkExport
|
||||
new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().bulkExport().systemExport().withResourceTypes(Lists.newArrayList("Patient", "Encounter", "Observation"))
|
||||
.build();
|
||||
}
|
||||
};
|
||||
//END SNIPPET: bulkExport
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2712
|
||||
title: "AuthorizationInterceptor can now be used to authorize bulk export requests"
|
|
@ -23,7 +23,7 @@ The AuthorizationInterceptor is used by subclassing it and then registering your
|
|||
|
||||
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.
|
||||
|
||||
## Authorizing Read Operations
|
||||
# Authorizing Read Operations
|
||||
|
||||
When authorizing a read operation, the AuthorizationInterceptor always allows client code to execute and generate a response. It then examines the response that would be returned before actually returning it to the client, and if rules do not permit that data to be shown to the client the interceptor aborts the request.
|
||||
|
||||
|
@ -33,7 +33,7 @@ See the following diagram for an example of how this works.
|
|||
|
||||
<img src="/hapi-fhir/docs/images/hapi_authorizationinterceptor_read_normal.svg" alt="Write Authorization"/>
|
||||
|
||||
## Authorizing Write Operations
|
||||
# Authorizing Write Operations
|
||||
|
||||
Write operations (create, update, etc.) are typically authorized by the interceptor by examining the parsed URL and making a decision about whether to authorize the operation before allowing Resource Provider code to proceed. This means that client code will not have a chance to execute and create resources that the client does not have permissions to create.
|
||||
|
||||
|
@ -41,9 +41,10 @@ See the following diagram for an example of how this works.
|
|||
|
||||
<img src="/hapi-fhir/docs/images/hapi_authorizationinterceptor_write_normal.svg" alt="Write Authorization"/>
|
||||
|
||||
|
||||
<a name="authorizing-sub-operations"/>
|
||||
|
||||
## Authorizing Sub-Operations
|
||||
# Authorizing Sub-Operations
|
||||
|
||||
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.
|
||||
|
||||
|
@ -55,7 +56,7 @@ In this type of situation, it is important to manually notify the interceptor ch
|
|||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|conditionalUpdate}}
|
||||
```
|
||||
|
||||
## Authorizing Patch Operations
|
||||
# Authorizing Patch Operations
|
||||
|
||||
The FHIR [patch](http://hl7.org/fhir/http.html#patch) operation presents a challenge for authorization, as the incoming request often contains very little detail about what is being modified.
|
||||
|
||||
|
@ -67,7 +68,7 @@ This should be combined with server support for [Authorizing Sub-Operations](#au
|
|||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|patchAll}}
|
||||
```
|
||||
|
||||
## Authorizing Multitenant Servers
|
||||
# Authorizing Multitenant Servers
|
||||
|
||||
The AuthorizationInterceptor has the ability to direct individual rules as only applying to a single tenant in a multitenant server. The following example shows such a rule.
|
||||
|
||||
|
@ -75,3 +76,10 @@ The AuthorizationInterceptor has the ability to direct individual rules as only
|
|||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|authorizeTenantAction}}
|
||||
```
|
||||
|
||||
# Authorizing Bulk Export Operations
|
||||
|
||||
AuthorizationInterceptor can be used to provide nuanced control over the kinds of Bulk Export operations that a user can initiate when using the JPA Server.
|
||||
|
||||
```java
|
||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|bulkExport}}
|
||||
```
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.4.0</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.bulk.export.api;
|
|||
|
||||
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportJobStatusEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
@ -36,6 +37,10 @@ public interface IBulkDataExportSvc {
|
|||
@Transactional(value = Transactional.TxType.NEVER)
|
||||
void purgeExpiredFiles();
|
||||
|
||||
/**
|
||||
* Deprecated - Use {@link #submitJob(BulkDataExportOptions, Boolean, RequestDetails)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions);
|
||||
|
||||
JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions, Boolean useCache, RequestDetails theRequestDetails);
|
||||
|
|
|
@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.bulk.export.job;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.bulk.export.job;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
|
|
|
@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.bulk.export.provider;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
|
||||
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportResponseJson;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
|
|
|
@ -32,7 +32,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
|||
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
||||
import ca.uhn.fhir.jpa.batch.BatchJobsConfig;
|
||||
import ca.uhn.fhir.jpa.batch.api.IBatchJobSubmitter;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
|
||||
import ca.uhn.fhir.jpa.bulk.export.job.BulkExportJobConfig;
|
||||
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportJobStatusEnum;
|
||||
|
@ -52,6 +52,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||
|
@ -80,9 +81,9 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions.ExportStyle.GROUP;
|
||||
import static ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions.ExportStyle.PATIENT;
|
||||
import static ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions.ExportStyle.SYSTEM;
|
||||
import static ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions.ExportStyle.GROUP;
|
||||
import static ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions.ExportStyle.PATIENT;
|
||||
import static ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions.ExportStyle.SYSTEM;
|
||||
import static ca.uhn.fhir.util.UrlUtil.escapeUrlParam;
|
||||
import static ca.uhn.fhir.util.UrlUtil.escapeUrlParams;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -299,6 +300,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
|
|||
|
||||
@Transactional
|
||||
@Override
|
||||
@Deprecated
|
||||
public JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions) {
|
||||
return submitJob(theBulkDataExportOptions, true, null);
|
||||
}
|
||||
|
@ -310,13 +312,6 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
|
|||
@Override
|
||||
public JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions, Boolean useCache, RequestDetails theRequestDetails) {
|
||||
|
||||
// Interceptor call: STORAGE_INITIATE_BULK_EXPORT
|
||||
HookParams params = new HookParams()
|
||||
.add(BulkDataExportOptions.class, theBulkDataExportOptions)
|
||||
.add(RequestDetails.class, theRequestDetails)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
|
||||
myInterceptorBroadcaster.callHooks(Pointcut.STORAGE_INITIATE_BULK_EXPORT, params);
|
||||
|
||||
String outputFormat = Constants.CT_FHIR_NDJSON;
|
||||
if (isNotBlank(theBulkDataExportOptions.getOutputFormat())) {
|
||||
outputFormat = theBulkDataExportOptions.getOutputFormat();
|
||||
|
@ -325,6 +320,13 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
|
|||
throw new InvalidRequestException("Invalid output format: " + theBulkDataExportOptions.getOutputFormat());
|
||||
}
|
||||
|
||||
// Interceptor call: STORAGE_INITIATE_BULK_EXPORT
|
||||
HookParams params = new HookParams()
|
||||
.add(BulkDataExportOptions.class, theBulkDataExportOptions)
|
||||
.add(RequestDetails.class, theRequestDetails)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
|
||||
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_INITIATE_BULK_EXPORT, params);
|
||||
|
||||
// TODO GGG KS can we encode BulkDataExportOptions as a JSON string as opposed to this request string. Feels like it would be a more extensible encoding...
|
||||
//Probably yes, but this will all need to be rebuilt when we remove this bridge entity
|
||||
StringBuilder requestBuilder = new StringBuilder();
|
||||
|
@ -445,7 +447,9 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
|
|||
}
|
||||
|
||||
private JobInfo toSubmittedJobInfo(BulkExportJobEntity theJob) {
|
||||
return new JobInfo().setJobId(theJob.getJobId());
|
||||
return new JobInfo()
|
||||
.setJobId(theJob.getJobId())
|
||||
.setStatus(theJob.getStatus());
|
||||
}
|
||||
|
||||
private void updateExpiry(BulkExportJobEntity theJob) {
|
||||
|
|
|
@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.bulk;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
|
||||
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportResponseJson;
|
||||
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportJobStatusEnum;
|
||||
|
|
|
@ -7,7 +7,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
|||
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||
import ca.uhn.fhir.jpa.batch.BatchJobsConfig;
|
||||
import ca.uhn.fhir.jpa.batch.api.IBatchJobSubmitter;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
|
||||
import ca.uhn.fhir.jpa.bulk.export.job.BulkExportJobParametersBuilder;
|
||||
import ca.uhn.fhir.jpa.bulk.export.job.GroupBulkExportJobParametersBuilder;
|
||||
|
@ -47,7 +47,6 @@ import org.hl7.fhir.r4.model.Patient;
|
|||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.batch.core.Job;
|
||||
|
|
|
@ -105,6 +105,38 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
|
|||
"Organization/ORG-P"
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevIncludesPagedSyncSearch() {
|
||||
int eocCount = 10;
|
||||
// myDaoConfig.setMaximumIncludesToLoadPerPage(5);
|
||||
|
||||
createOrganizationWithReferencingEpisodesOfCare(eocCount);
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap()
|
||||
.add("_id", new TokenParam("ORG-0"))
|
||||
.addRevInclude(EpisodeOfCare.INCLUDE_ORGANIZATION);
|
||||
myCaptureQueriesListener.clear();
|
||||
IBundleProvider results = myOrganizationDao.search(map);
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(results);
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertThat(ids.toString(), ids, containsInAnyOrder(
|
||||
"EpisodeOfCare/EOC-0",
|
||||
"EpisodeOfCare/EOC-1",
|
||||
"EpisodeOfCare/EOC-2",
|
||||
"EpisodeOfCare/EOC-3",
|
||||
"EpisodeOfCare/EOC-4",
|
||||
"EpisodeOfCare/EOC-5",
|
||||
"EpisodeOfCare/EOC-6",
|
||||
"EpisodeOfCare/EOC-7",
|
||||
"EpisodeOfCare/EOC-8",
|
||||
"EpisodeOfCare/EOC-9",
|
||||
"Organization/ORG-0"
|
||||
));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void createOrganizationWithReferencingEpisodesOfCare(int theEocCount) {
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
|
||||
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportJobStatusEnum;
|
||||
import ca.uhn.fhir.jpa.bulk.export.provider.BulkDataExportProvider;
|
||||
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||
|
@ -16,7 +20,10 @@ import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRuleTester;
|
|||
import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
|
||||
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import com.github.jsonldjava.shaded.com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
|
@ -41,10 +48,13 @@ import org.hl7.fhir.r4.model.Patient;
|
|||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -62,6 +72,9 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
|||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptorJpaR4Test.class);
|
||||
|
||||
@Autowired
|
||||
private IBulkDataExportSvc myBulkDataExportSvc;
|
||||
|
||||
@BeforeEach
|
||||
@Override
|
||||
public void before() throws Exception {
|
||||
|
@ -69,6 +82,293 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
|||
myDaoConfig.setAllowMultipleDelete(true);
|
||||
myDaoConfig.setExpungeEnabled(true);
|
||||
myDaoConfig.setDeleteExpungeEnabled(true);
|
||||
ourRestServer.registerInterceptor(new BulkDataExportProvider());
|
||||
}
|
||||
|
||||
@Override
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
super.after();
|
||||
myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof AuthorizationInterceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBulkExport_AuthorizeGroupId() {
|
||||
|
||||
AuthorizationInterceptor authInterceptor = new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().bulkExport().groupExportOnGroup(new IdType("Group/123")).andThen()
|
||||
.build();
|
||||
}
|
||||
};
|
||||
myInterceptorRegistry.registerInterceptor(authInterceptor);
|
||||
|
||||
/*
|
||||
* Matching group ID
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setGroupId(new IdType("Group/123"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.GROUP);
|
||||
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
assertEquals(BulkExportJobStatusEnum.SUBMITTED, jobDetails.getStatus());
|
||||
}
|
||||
|
||||
/*
|
||||
* Non matching group ID
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setGroupId(new IdType("Group/456"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.GROUP);
|
||||
|
||||
try {
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Non group export
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.SYSTEM);
|
||||
|
||||
try {
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBulkExport_AuthorizePatientId() {
|
||||
|
||||
AuthorizationInterceptor authInterceptor = new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().bulkExport().patientExportOnGroup(new IdType("Group/123")).andThen()
|
||||
.build();
|
||||
}
|
||||
};
|
||||
myInterceptorRegistry.registerInterceptor(authInterceptor);
|
||||
|
||||
/*
|
||||
* Matching group ID
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setGroupId(new IdType("Group/123"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.PATIENT);
|
||||
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
assertEquals(BulkExportJobStatusEnum.SUBMITTED, jobDetails.getStatus());
|
||||
}
|
||||
|
||||
/*
|
||||
* Non matching group ID
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setGroupId(new IdType("Group/456"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.PATIENT);
|
||||
|
||||
try {
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Non group export
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setGroupId(new IdType("Group/456"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.GROUP);
|
||||
|
||||
try {
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBulkExport_AuthorizeSystem() {
|
||||
|
||||
AuthorizationInterceptor authInterceptor = new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().bulkExport().systemExport().andThen()
|
||||
.build();
|
||||
}
|
||||
};
|
||||
myInterceptorRegistry.registerInterceptor(authInterceptor);
|
||||
|
||||
/*
|
||||
* System export
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setGroupId(new IdType("Group/456"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.SYSTEM);
|
||||
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
assertEquals(BulkExportJobStatusEnum.SUBMITTED, jobDetails.getStatus());
|
||||
}
|
||||
|
||||
/*
|
||||
* Patient export
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setGroupId(new IdType("Group/456"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.PATIENT);
|
||||
|
||||
try {
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testBulkExport_AuthorizeAny() {
|
||||
|
||||
AuthorizationInterceptor authInterceptor = new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().bulkExport().any().andThen()
|
||||
.build();
|
||||
}
|
||||
};
|
||||
myInterceptorRegistry.registerInterceptor(authInterceptor);
|
||||
|
||||
/*
|
||||
* System export
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setGroupId(new IdType("Group/456"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.SYSTEM);
|
||||
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
assertEquals(BulkExportJobStatusEnum.SUBMITTED, jobDetails.getStatus());
|
||||
}
|
||||
|
||||
/*
|
||||
* Patient export
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setGroupId(new IdType("Group/456"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.PATIENT);
|
||||
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
assertEquals(BulkExportJobStatusEnum.SUBMITTED, jobDetails.getStatus());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBulkExport_SpecificResourceTypesEnforced() {
|
||||
|
||||
AuthorizationInterceptor authInterceptor = new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().bulkExport().systemExport().withResourceTypes(Lists.newArrayList("Patient", "Encounter")).andThen()
|
||||
.build();
|
||||
}
|
||||
};
|
||||
myInterceptorRegistry.registerInterceptor(authInterceptor);
|
||||
|
||||
/*
|
||||
* Appropriate Resources
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setResourceTypes(Sets.newHashSet("Patient", "Encounter"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.SYSTEM);
|
||||
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
IBulkDataExportSvc.JobInfo jobDetails = myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
assertEquals(BulkExportJobStatusEnum.SUBMITTED, jobDetails.getStatus());
|
||||
}
|
||||
|
||||
/*
|
||||
* Inappropriate Resources
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setResourceTypes(Sets.newHashSet("Patient", "Encounter", "Observation"));
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.SYSTEM);
|
||||
|
||||
try {
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* No Resources
|
||||
*/
|
||||
{
|
||||
BulkDataExportOptions bulkDataExportOptions = new BulkDataExportOptions();
|
||||
bulkDataExportOptions.setExportStyle(BulkDataExportOptions.ExportStyle.SYSTEM);
|
||||
|
||||
try {
|
||||
ServletRequestDetails requestDetails = new ServletRequestDetails().setServletRequest(new MockHttpServletRequest());
|
||||
myBulkDataExportSvc.submitJob(bulkDataExportOptions, true, requestDetails);
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.bulk.export.api;
|
||||
package ca.uhn.fhir.rest.api.server.bulk;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* HAPI FHIR - Server Framework
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
|
@ -27,6 +27,7 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
|
|||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.consent.ConsentInterceptor;
|
||||
import com.google.common.collect.Lists;
|
||||
|
@ -68,6 +69,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
@Interceptor
|
||||
public class AuthorizationInterceptor implements IRuleApplier {
|
||||
|
||||
public static final String REQUEST_ATTRIBUTE_BULK_DATA_EXPORT_OPTIONS = AuthorizationInterceptor.class.getName() + "_BulkDataExportOptions";
|
||||
private static final AtomicInteger ourInstanceCount = new AtomicInteger(0);
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptor.class);
|
||||
private final int myInstanceIndex = ourInstanceCount.incrementAndGet();
|
||||
|
@ -351,6 +353,16 @@ public class AuthorizationInterceptor implements IRuleApplier {
|
|||
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, null, null, null, thePointcut);
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_INITIATE_BULK_EXPORT)
|
||||
public void initiateBulkExport(RequestDetails theRequestDetails, BulkDataExportOptions theBulkExportOptions, Pointcut thePointcut) {
|
||||
RestOperationTypeEnum restOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER;
|
||||
if (theRequestDetails != null) {
|
||||
theRequestDetails.setAttribute(REQUEST_ATTRIBUTE_BULK_DATA_EXPORT_OPTIONS, theBulkExportOptions);
|
||||
}
|
||||
applyRulesAndFailIfDeny(restOperationType, theRequestDetails, null, null, null, thePointcut);
|
||||
}
|
||||
|
||||
|
||||
private void checkPointcutAndFailIfDeny(RequestDetails theRequestDetails, Pointcut thePointcut, @Nonnull IBaseResource theInputResource) {
|
||||
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, theInputResource, theInputResource.getIdElement(), null, thePointcut);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -121,4 +121,10 @@ public interface IAuthRuleBuilderRule {
|
|||
*/
|
||||
IAuthRuleBuilderGraphQL graphQL();
|
||||
|
||||
/**
|
||||
* This rule permits the user to initiate a FHIR bulk export
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
IAuthRuleBuilderRuleBulkExport bulkExport();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Server Framework
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public interface IAuthRuleBuilderRuleBulkExport {
|
||||
|
||||
/**
|
||||
* Allow/deny <b>group-level</b> export rule applies to the Group with the given resource ID, e.g. <code>Group/123</code>
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
default IAuthRuleBuilderRuleBulkExportWithTarget groupExportOnGroup(@Nonnull IIdType theFocusResourceId) {
|
||||
return groupExportOnGroup(theFocusResourceId.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow/deny <b>group-level</b> export rule applies to the Group with the given resource ID, e.g. <code>Group/123</code>
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
IAuthRuleBuilderRuleBulkExportWithTarget groupExportOnGroup(@Nonnull String theFocusResourceId);
|
||||
|
||||
/**
|
||||
* Allow/deny <b>patient-level</b> export rule applies to the Group with the given resource ID, e.g. <code>Group/123</code>
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
default IAuthRuleBuilderRuleBulkExportWithTarget patientExportOnGroup(@Nonnull IIdType theFocusResourceId) {
|
||||
return patientExportOnGroup(theFocusResourceId.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow/deny <b>patient-level</b> export rule applies to the Group with the given resource ID, e.g. <code>Group/123</code>
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
IAuthRuleBuilderRuleBulkExportWithTarget patientExportOnGroup(@Nonnull String theFocusResourceId);
|
||||
|
||||
/**
|
||||
* Allow/deny <b>system-level</b> export rule applies to the Group with the given resource ID, e.g. <code>Group/123</code>
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
IAuthRuleBuilderRuleBulkExportWithTarget systemExport();
|
||||
|
||||
/**
|
||||
* Allow/deny <b>any bulk export</b> operation
|
||||
*/
|
||||
IAuthRuleBuilderRuleBulkExportWithTarget any();
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Server Framework
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public interface IAuthRuleBuilderRuleBulkExportWithTarget extends IAuthRuleFinished {
|
||||
|
||||
/**
|
||||
* If specified, rule will only apply to bulk export requests that explicitly specify a list
|
||||
* of resource types where the list is equal to, or a subset of the supplied collection.
|
||||
*
|
||||
* This this method is not called, there will be no restriction on the resource types
|
||||
* that a user can initiate a bulk export on.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
IAuthRuleBuilderRuleBulkExportWithTarget withResourceTypes(Collection<String> theResourceTypes);
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ import org.apache.commons.lang3.Validate;
|
|||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -311,6 +312,11 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
return new RuleBuilderGraphQL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleBulkExport bulkExport() {
|
||||
return new RuleBuilderBulkExport();
|
||||
}
|
||||
|
||||
private class RuleBuilderRuleConditional implements IAuthRuleBuilderRuleConditional {
|
||||
|
||||
private AppliesTypeEnum myAppliesTo;
|
||||
|
@ -702,6 +708,65 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
}
|
||||
|
||||
private class RuleBuilderBulkExport implements IAuthRuleBuilderRuleBulkExport {
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleBulkExportWithTarget groupExportOnGroup(@Nonnull String theFocusResourceId) {
|
||||
RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
|
||||
rule.setAppliesToGroupExportOnGroup(theFocusResourceId);
|
||||
rule.setMode(myRuleMode);
|
||||
myRules.add(rule);
|
||||
|
||||
return new RuleBuilderBulkExportWithTarget(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleBulkExportWithTarget patientExportOnGroup(@Nonnull String theFocusResourceId) {
|
||||
RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
|
||||
rule.setAppliesToPatientExportOnGroup(theFocusResourceId);
|
||||
rule.setMode(myRuleMode);
|
||||
myRules.add(rule);
|
||||
|
||||
return new RuleBuilderBulkExportWithTarget(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleBulkExportWithTarget systemExport() {
|
||||
RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
|
||||
rule.setAppliesToSystem();
|
||||
rule.setMode(myRuleMode);
|
||||
myRules.add(rule);
|
||||
|
||||
return new RuleBuilderBulkExportWithTarget(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleBulkExportWithTarget any() {
|
||||
RuleBulkExportImpl rule = new RuleBulkExportImpl(myRuleName);
|
||||
rule.setAppliesToAny();
|
||||
rule.setMode(myRuleMode);
|
||||
myRules.add(rule);
|
||||
|
||||
return new RuleBuilderBulkExportWithTarget(rule);
|
||||
}
|
||||
|
||||
private class RuleBuilderBulkExportWithTarget extends RuleBuilderFinished implements IAuthRuleBuilderRuleBulkExportWithTarget {
|
||||
private final RuleBulkExportImpl myRule;
|
||||
|
||||
private RuleBuilderBulkExportWithTarget(RuleBulkExportImpl theRule) {
|
||||
super(theRule);
|
||||
myRule = theRule;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleBulkExportWithTarget withResourceTypes(Collection<String> theResourceTypes) {
|
||||
myRule.setResourceTypes(theResourceTypes);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String toTypeName(Class<? extends IBaseResource> theType) {
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Server Framework
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class RuleBulkExportImpl extends BaseRule {
|
||||
private String myGroupId;
|
||||
private BulkDataExportOptions.ExportStyle myWantExportStyle;
|
||||
private Collection<String> myResourceTypes;
|
||||
private boolean myWantAnyStyle;
|
||||
|
||||
RuleBulkExportImpl(String theRuleName) {
|
||||
super(theRuleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationInterceptor.Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags, Pointcut thePointcut) {
|
||||
if (thePointcut != Pointcut.STORAGE_INITIATE_BULK_EXPORT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (theRequestDetails == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BulkDataExportOptions options = (BulkDataExportOptions) theRequestDetails.getAttribute(AuthorizationInterceptor.REQUEST_ATTRIBUTE_BULK_DATA_EXPORT_OPTIONS);
|
||||
|
||||
if (!myWantAnyStyle && options.getExportStyle() != myWantExportStyle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (myResourceTypes != null && !myResourceTypes.isEmpty()) {
|
||||
if (options.getResourceTypes() == null) {
|
||||
return null;
|
||||
}
|
||||
for (String next : options.getResourceTypes()) {
|
||||
if (!myResourceTypes.contains(next)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (myWantAnyStyle || myWantExportStyle == BulkDataExportOptions.ExportStyle.SYSTEM) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
|
||||
if (isNotBlank(myGroupId) && options.getGroupId() != null) {
|
||||
String expectedGroupId = new IdDt(myGroupId).toUnqualifiedVersionless().getValue();
|
||||
String actualGroupId = options.getGroupId().toUnqualifiedVersionless().getValue();
|
||||
if (Objects.equals(expectedGroupId, actualGroupId)) {
|
||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAppliesToGroupExportOnGroup(String theGroupId) {
|
||||
myWantExportStyle = BulkDataExportOptions.ExportStyle.GROUP;
|
||||
myGroupId = theGroupId;
|
||||
}
|
||||
|
||||
public void setAppliesToPatientExportOnGroup(String theGroupId) {
|
||||
myWantExportStyle = BulkDataExportOptions.ExportStyle.PATIENT;
|
||||
myGroupId = theGroupId;
|
||||
}
|
||||
|
||||
public void setAppliesToSystem() {
|
||||
myWantExportStyle = BulkDataExportOptions.ExportStyle.SYSTEM;
|
||||
}
|
||||
|
||||
public void setResourceTypes(Collection<String> theResourceTypes) {
|
||||
myResourceTypes = theResourceTypes;
|
||||
}
|
||||
|
||||
public void setAppliesToAny() {
|
||||
myWantAnyStyle = true;
|
||||
}
|
||||
}
|
|
@ -1,278 +0,0 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Server Framework
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2020 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.util.ServletRequestUtil;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* This interceptor can be used to automatically narrow the scope of searches in order to
|
||||
* automatically restrict the searches to specific compartments.
|
||||
* <p>
|
||||
* For example, this interceptor
|
||||
* could be used to restrict a user to only viewing data belonging to Patient/123 (i.e. data
|
||||
* in the <code>Patient/123</code> compartment). In this case, a user performing a search
|
||||
* for<br/>
|
||||
* <code>http://baseurl/Observation?category=laboratory</code><br/>
|
||||
* would receive results as though they had requested<br/>
|
||||
* <code>http://baseurl/Observation?subject=Patient/123&category=laboratory</code>
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this interceptor should be used in combination with {@link AuthorizationInterceptor}
|
||||
* if you are restricting results because of a security restriction. This interceptor is not
|
||||
* intended to be a failsafe way of preventing users from seeing the wrong data (that is the
|
||||
* purpose of AuthorizationInterceptor). This interceptor is simply intended as a convenience to
|
||||
* help users simplify their queries while not receiving security errors for to trying to access
|
||||
* data they do not have access to see.
|
||||
* </p>
|
||||
*
|
||||
* @see AuthorizationInterceptor
|
||||
*/
|
||||
public class SearchNarrowingInterceptor {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(SearchNarrowingInterceptor.class);
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses should override this method to supply the set of compartments that
|
||||
* the user making the request should actually have access to.
|
||||
* <p>
|
||||
* Typically this is done by examining <code>theRequestDetails</code> to find
|
||||
* out who the current user is and then building a list of Strings.
|
||||
* </p>
|
||||
*
|
||||
* @param theRequestDetails The individual request currently being applied
|
||||
* @return The list of allowed compartments and instances that should be used
|
||||
* for search narrowing. If this method returns <code>null</code>, no narrowing will
|
||||
* be performed
|
||||
*/
|
||||
protected AuthorizedList buildAuthorizedList(@SuppressWarnings("unused") RequestDetails theRequestDetails) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)
|
||||
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
|
||||
// We don't support this operation type yet
|
||||
Validate.isTrue(theRequestDetails.getRestOperationType() != RestOperationTypeEnum.SEARCH_SYSTEM);
|
||||
|
||||
if (theRequestDetails.getRestOperationType() != RestOperationTypeEnum.SEARCH_TYPE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||
RuntimeResourceDefinition resDef = ctx.getResourceDefinition(theRequestDetails.getResourceName());
|
||||
HashMap<String, List<String>> parameterToOrValues = new HashMap<>();
|
||||
AuthorizedList authorizedList = buildAuthorizedList(theRequestDetails);
|
||||
if (authorizedList == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a map of search parameter values that need to be added to the
|
||||
* given request
|
||||
*/
|
||||
Collection<String> compartments = authorizedList.getAllowedCompartments();
|
||||
if (compartments != null) {
|
||||
processResourcesOrCompartments(theRequestDetails, resDef, parameterToOrValues, compartments, true);
|
||||
}
|
||||
Collection<String> resources = authorizedList.getAllowedInstances();
|
||||
if (resources != null) {
|
||||
processResourcesOrCompartments(theRequestDetails, resDef, parameterToOrValues, resources, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add any param values to the actual request
|
||||
*/
|
||||
if (parameterToOrValues.size() > 0) {
|
||||
Map<String, String[]> newParameters = new HashMap<>(theRequestDetails.getParameters());
|
||||
for (Map.Entry<String, List<String>> nextEntry : parameterToOrValues.entrySet()) {
|
||||
String nextParamName = nextEntry.getKey();
|
||||
List<String> nextAllowedValues = nextEntry.getValue();
|
||||
|
||||
if (!newParameters.containsKey(nextParamName)) {
|
||||
|
||||
/*
|
||||
* If we don't already have a parameter of the given type, add one
|
||||
*/
|
||||
String nextValuesJoined = ParameterUtil.escapeAndJoinOrList(nextAllowedValues);
|
||||
String[] paramValues = {nextValuesJoined};
|
||||
newParameters.put(nextParamName, paramValues);
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* If the client explicitly requested the given parameter already, we'll
|
||||
* just update the request to have the intersection of the values that the client
|
||||
* requested, and the values that the user is allowed to see
|
||||
*/
|
||||
String[] existingValues = newParameters.get(nextParamName);
|
||||
boolean restrictedExistingList = false;
|
||||
for (int i = 0; i < existingValues.length; i++) {
|
||||
|
||||
String nextExistingValue = existingValues[i];
|
||||
List<String> nextRequestedValues = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, nextExistingValue);
|
||||
List<String> nextPermittedValues = ListUtils.intersection(nextRequestedValues, nextAllowedValues);
|
||||
if (nextPermittedValues.size() > 0) {
|
||||
restrictedExistingList = true;
|
||||
existingValues[i] = ParameterUtil.escapeAndJoinOrList(nextPermittedValues);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* If none of the values that were requested by the client overlap at all
|
||||
* with the values that the user is allowed to see, we'll just add the permitted
|
||||
* list as a new list. Ultimately this scenario actually means that the client
|
||||
* shouldn't get *any* results back, and adding a new AND parameter (that doesn't
|
||||
* overlap at all with the others) is one way of ensuring that.
|
||||
*/
|
||||
if (!restrictedExistingList) {
|
||||
String[] newValues = Arrays.copyOf(existingValues, existingValues.length + 1);
|
||||
newValues[existingValues.length] = ParameterUtil.escapeAndJoinOrList(nextAllowedValues);
|
||||
newParameters.put(nextParamName, newValues);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
theRequestDetails.setParameters(newParameters);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED)
|
||||
public void incomingRequestPreHandled(ServletRequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
|
||||
if (theRequestDetails.getRestOperationType() != RestOperationTypeEnum.TRANSACTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
IBaseBundle bundle = (IBaseBundle) theRequestDetails.getResource();
|
||||
FhirContext ctx = theRequestDetails.getFhirContext();
|
||||
BundleEntryUrlProcessor processor = new BundleEntryUrlProcessor(ctx, theRequestDetails, theRequest, theResponse);
|
||||
BundleUtil.processEntries(ctx, bundle, processor);
|
||||
}
|
||||
|
||||
private class BundleEntryUrlProcessor implements Consumer<ModifiableBundleEntry> {
|
||||
private final FhirContext myFhirContext;
|
||||
private final ServletRequestDetails myRequestDetails;
|
||||
private final HttpServletRequest myRequest;
|
||||
private final HttpServletResponse myResponse;
|
||||
|
||||
public BundleEntryUrlProcessor(FhirContext theFhirContext, ServletRequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) {
|
||||
myFhirContext = theFhirContext;
|
||||
myRequestDetails = theRequestDetails;
|
||||
myRequest = theRequest;
|
||||
myResponse = theResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ModifiableBundleEntry theModifiableBundleEntry) {
|
||||
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
|
||||
|
||||
String url = theModifiableBundleEntry.getRequestUrl();
|
||||
|
||||
ServletSubRequestDetails subServletRequestDetails = ServletRequestUtil.getServletSubRequestDetails(myRequestDetails, url, paramValues);
|
||||
BaseMethodBinding<?> method = subServletRequestDetails.getServer().determineResourceMethod(subServletRequestDetails, url);
|
||||
RestOperationTypeEnum restOperationType = method.getRestOperationType();
|
||||
subServletRequestDetails.setRestOperationType(restOperationType);
|
||||
|
||||
incomingRequestPostProcessed(subServletRequestDetails, myRequest, myResponse);
|
||||
|
||||
theModifiableBundleEntry.setRequestUrl(myFhirContext, ServletRequestUtil.extractUrl(subServletRequestDetails));
|
||||
}
|
||||
}
|
||||
|
||||
private void processResourcesOrCompartments(RequestDetails theRequestDetails, RuntimeResourceDefinition theResDef, HashMap<String, List<String>> theParameterToOrValues, Collection<String> theResourcesOrCompartments, boolean theAreCompartments) {
|
||||
String lastCompartmentName = null;
|
||||
String lastSearchParamName = null;
|
||||
for (String nextCompartment : theResourcesOrCompartments) {
|
||||
Validate.isTrue(StringUtils.countMatches(nextCompartment, '/') == 1, "Invalid compartment name (must be in form \"ResourceType/xxx\": %s", nextCompartment);
|
||||
String compartmentName = nextCompartment.substring(0, nextCompartment.indexOf('/'));
|
||||
|
||||
String searchParamName = null;
|
||||
if (compartmentName.equalsIgnoreCase(lastCompartmentName)) {
|
||||
|
||||
// Avoid doing a lookup for the same thing repeatedly
|
||||
searchParamName = lastSearchParamName;
|
||||
|
||||
} else {
|
||||
|
||||
if (compartmentName.equalsIgnoreCase(theRequestDetails.getResourceName())) {
|
||||
|
||||
searchParamName = "_id";
|
||||
|
||||
} else if (theAreCompartments) {
|
||||
|
||||
List<RuntimeSearchParam> searchParams = theResDef.getSearchParamsForCompartmentName(compartmentName);
|
||||
if (searchParams.size() > 0) {
|
||||
|
||||
// Resources like Observation have several fields that add the resource to
|
||||
// the compartment. In the case of Observation, it's subject, patient and performer.
|
||||
// For this kind of thing, we'll prefer the one called "patient".
|
||||
RuntimeSearchParam searchParam =
|
||||
searchParams
|
||||
.stream()
|
||||
.filter(t -> t.getName().equalsIgnoreCase(compartmentName))
|
||||
.findFirst()
|
||||
.orElse(searchParams.get(0));
|
||||
searchParamName = searchParam.getName();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
lastCompartmentName = compartmentName;
|
||||
lastSearchParamName = searchParamName;
|
||||
|
||||
}
|
||||
|
||||
if (searchParamName != null) {
|
||||
List<String> orValues = theParameterToOrValues.computeIfAbsent(searchParamName, t -> new ArrayList<>());
|
||||
orValues.add(nextCompartment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -170,8 +170,9 @@ public class ServletRequestDetails extends RequestDetails {
|
|||
this.myServer = theServer;
|
||||
}
|
||||
|
||||
public void setServletRequest(HttpServletRequest myServletRequest) {
|
||||
public ServletRequestDetails setServletRequest(HttpServletRequest myServletRequest) {
|
||||
this.myServletRequest = myServletRequest;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setServletResponse(HttpServletResponse myServletResponse) {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -58,37 +58,37 @@
|
|||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-dstu3</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-r4</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-r5</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-validation-resources-dstu3</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-validation-resources-r4</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -6,7 +6,7 @@
|
|||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<name>HAPI-FHIR</name>
|
||||
<description>An open-source implementation of the FHIR specification in Java.</description>
|
||||
<url>https://hapifhir.io</url>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.5.0-PRE1-SNAPSHOT</version>
|
||||
<version>5.5.0-PRE2-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
Loading…
Reference in New Issue