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:
James Agnew 2021-06-06 20:47:43 -04:00 committed by GitHub
parent 4f1f09abb1
commit dc627dc019
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 770 additions and 371 deletions

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -1038,7 +1038,7 @@ public enum Pointcut implements IPointcut {
*/ */
STORAGE_INITIATE_BULK_EXPORT( STORAGE_INITIATE_BULK_EXPORT(
void.class, 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.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
), ),

View File

@ -3,14 +3,14 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId> <artifactId>hapi-fhir-bom</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>HAPI FHIR BOM</name> <name>HAPI FHIR BOM</name>
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId> <artifactId>hapi-fhir-cli</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath> <relativePath>../../hapi-deployable-pom</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.interceptor.auth.*; import ca.uhn.fhir.rest.server.interceptor.auth.*;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 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.dstu3.model.IdType;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
@ -192,6 +193,19 @@ public class AuthorizationInterceptors {
}; };
//END SNIPPET: patchAll //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
} }

View File

@ -0,0 +1,4 @@
---
type: add
issue: 2712
title: "AuthorizationInterceptor can now be used to authorize bulk export requests"

View File

@ -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. 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. 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"/> <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. 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"/> <img src="/hapi-fhir/docs/images/hapi_authorizationinterceptor_write_normal.svg" alt="Write Authorization"/>
<a name="authorizing-sub-operations"/> <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. 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}} {{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. 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}} {{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. 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}} {{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}}
```

View File

@ -11,7 +11,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>5.4.0</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -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.jpa.bulk.export.model.BulkExportJobStatusEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails; 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 org.hl7.fhir.instance.model.api.IIdType;
import javax.transaction.Transactional; import javax.transaction.Transactional;
@ -36,6 +37,10 @@ public interface IBulkDataExportSvc {
@Transactional(value = Transactional.TxType.NEVER) @Transactional(value = Transactional.TxType.NEVER)
void purgeExpiredFiles(); void purgeExpiredFiles();
/**
* Deprecated - Use {@link #submitJob(BulkDataExportOptions, Boolean, RequestDetails)} instead
*/
@Deprecated
JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions); JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions);
JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions, Boolean useCache, RequestDetails theRequestDetails); JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions, Boolean useCache, RequestDetails theRequestDetails);

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.bulk.export.job;
* #L% * #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 ca.uhn.fhir.rest.api.Constants;
import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.JobParametersBuilder;

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.bulk.export.job;
* #L% * #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.jpa.bulk.export.api.IBulkDataExportSvc;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;

View File

@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.bulk.export.provider;
*/ */
import ca.uhn.fhir.context.FhirContext; 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.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportResponseJson; import ca.uhn.fhir.jpa.bulk.export.model.BulkExportResponseJson;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;

View File

@ -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.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.batch.BatchJobsConfig; import ca.uhn.fhir.jpa.batch.BatchJobsConfig;
import ca.uhn.fhir.jpa.batch.api.IBatchJobSubmitter; 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.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.bulk.export.job.BulkExportJobConfig; import ca.uhn.fhir.jpa.bulk.export.job.BulkExportJobConfig;
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportJobStatusEnum; 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.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseBinary; import org.hl7.fhir.instance.model.api.IBaseBinary;
@ -80,9 +81,9 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions.ExportStyle.GROUP; import static ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions.ExportStyle.GROUP;
import static ca.uhn.fhir.jpa.bulk.export.api.BulkDataExportOptions.ExportStyle.PATIENT; import static ca.uhn.fhir.rest.api.server.bulk.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.SYSTEM;
import static ca.uhn.fhir.util.UrlUtil.escapeUrlParam; import static ca.uhn.fhir.util.UrlUtil.escapeUrlParam;
import static ca.uhn.fhir.util.UrlUtil.escapeUrlParams; import static ca.uhn.fhir.util.UrlUtil.escapeUrlParams;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -299,6 +300,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
@Transactional @Transactional
@Override @Override
@Deprecated
public JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions) { public JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions) {
return submitJob(theBulkDataExportOptions, true, null); return submitJob(theBulkDataExportOptions, true, null);
} }
@ -310,13 +312,6 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
@Override @Override
public JobInfo submitJob(BulkDataExportOptions theBulkDataExportOptions, Boolean useCache, RequestDetails theRequestDetails) { 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; String outputFormat = Constants.CT_FHIR_NDJSON;
if (isNotBlank(theBulkDataExportOptions.getOutputFormat())) { if (isNotBlank(theBulkDataExportOptions.getOutputFormat())) {
outputFormat = theBulkDataExportOptions.getOutputFormat(); outputFormat = theBulkDataExportOptions.getOutputFormat();
@ -325,6 +320,13 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
throw new InvalidRequestException("Invalid output format: " + theBulkDataExportOptions.getOutputFormat()); 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... // 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 //Probably yes, but this will all need to be rebuilt when we remove this bridge entity
StringBuilder requestBuilder = new StringBuilder(); StringBuilder requestBuilder = new StringBuilder();
@ -445,7 +447,9 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
} }
private JobInfo toSubmittedJobInfo(BulkExportJobEntity theJob) { 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) { private void updateExpiry(BulkExportJobEntity theJob) {

View File

@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.bulk;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 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.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportResponseJson; import ca.uhn.fhir.jpa.bulk.export.model.BulkExportResponseJson;
import ca.uhn.fhir.jpa.bulk.export.model.BulkExportJobStatusEnum; import ca.uhn.fhir.jpa.bulk.export.model.BulkExportJobStatusEnum;

View File

@ -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.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.batch.BatchJobsConfig; import ca.uhn.fhir.jpa.batch.BatchJobsConfig;
import ca.uhn.fhir.jpa.batch.api.IBatchJobSubmitter; 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.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.bulk.export.job.BulkExportJobParametersBuilder; import ca.uhn.fhir.jpa.bulk.export.job.BulkExportJobParametersBuilder;
import ca.uhn.fhir.jpa.bulk.export.job.GroupBulkExportJobParametersBuilder; 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.hl7.fhir.r4.model.Reference;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job; import org.springframework.batch.core.Job;

View File

@ -105,6 +105,38 @@ public class FhirResourceDaoR4SearchIncludeTest extends BaseJpaR4Test {
"Organization/ORG-P" "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) { private void createOrganizationWithReferencingEpisodesOfCare(int theEocCount) {

View File

@ -1,11 +1,15 @@
package ca.uhn.fhir.jpa.provider.r4; 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.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails; 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.client.interceptor.SimpleRequestHeaderInterceptor;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; 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.PolicyEnum;
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder; import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.UrlUtil; 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.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete; 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.Practitioner;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -62,6 +72,9 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptorJpaR4Test.class); private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptorJpaR4Test.class);
@Autowired
private IBulkDataExportSvc myBulkDataExportSvc;
@BeforeEach @BeforeEach
@Override @Override
public void before() throws Exception { public void before() throws Exception {
@ -69,6 +82,293 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
myDaoConfig.setAllowMultipleDelete(true); myDaoConfig.setAllowMultipleDelete(true);
myDaoConfig.setExpungeEnabled(true); myDaoConfig.setExpungeEnabled(true);
myDaoConfig.setDeleteExpungeEnabled(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
}
}
} }
/** /**

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -1,8 +1,8 @@
package ca.uhn.fhir.jpa.bulk.export.api; package ca.uhn.fhir.rest.api.server.bulk;
/*- /*-
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR - Server Framework
* %% * %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc. * Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %% * %%

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails; 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.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.interceptor.consent.ConsentInterceptor; import ca.uhn.fhir.rest.server.interceptor.consent.ConsentInterceptor;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -68,6 +69,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Interceptor @Interceptor
public class AuthorizationInterceptor implements IRuleApplier { 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 AtomicInteger ourInstanceCount = new AtomicInteger(0);
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptor.class); private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptor.class);
private final int myInstanceIndex = ourInstanceCount.incrementAndGet(); private final int myInstanceIndex = ourInstanceCount.incrementAndGet();
@ -351,6 +353,16 @@ public class AuthorizationInterceptor implements IRuleApplier {
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, null, null, null, thePointcut); 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) { private void checkPointcutAndFailIfDeny(RequestDetails theRequestDetails, Pointcut thePointcut, @Nonnull IBaseResource theInputResource) {
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, theInputResource, theInputResource.getIdElement(), null, thePointcut); applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, theInputResource, theInputResource.getIdElement(), null, thePointcut);
} }

View File

@ -29,6 +29,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;

View File

@ -121,4 +121,10 @@ public interface IAuthRuleBuilderRule {
*/ */
IAuthRuleBuilderGraphQL graphQL(); IAuthRuleBuilderGraphQL graphQL();
/**
* This rule permits the user to initiate a FHIR bulk export
*
* @since 5.5.0
*/
IAuthRuleBuilderRuleBulkExport bulkExport();
} }

View File

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

View File

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

View File

@ -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.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -311,6 +312,11 @@ public class RuleBuilder implements IAuthRuleBuilder {
return new RuleBuilderGraphQL(); return new RuleBuilderGraphQL();
} }
@Override
public IAuthRuleBuilderRuleBulkExport bulkExport() {
return new RuleBuilderBulkExport();
}
private class RuleBuilderRuleConditional implements IAuthRuleBuilderRuleConditional { private class RuleBuilderRuleConditional implements IAuthRuleBuilderRuleConditional {
private AppliesTypeEnum myAppliesTo; private AppliesTypeEnum myAppliesTo;
@ -702,6 +708,65 @@ public class RuleBuilder implements IAuthRuleBuilder {
return new RuleBuilderFinished(rule); 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) { private static String toTypeName(Class<? extends IBaseResource> theType) {

View File

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

View File

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

View File

@ -170,8 +170,9 @@ public class ServletRequestDetails extends RequestDetails {
this.myServer = theServer; this.myServer = theServer;
} }
public void setServletRequest(HttpServletRequest myServletRequest) { public ServletRequestDetails setServletRequest(HttpServletRequest myServletRequest) {
this.myServletRequest = myServletRequest; this.myServletRequest = myServletRequest;
return this;
} }
public void setServletResponse(HttpServletResponse myServletResponse) { public void setServletResponse(HttpServletResponse myServletResponse) {

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId> <artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</parent> </parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId> <artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId> <artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</parent> </parent>
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId> <artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId> <artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</parent> </parent>
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId> <artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId> <artifactId>hapi-fhir-spring-boot</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</parent> </parent>
<artifactId>hapi-fhir-spring-boot-samples</artifactId> <artifactId>hapi-fhir-spring-boot-samples</artifactId>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <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> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
@ -58,37 +58,37 @@
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId> <artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId> <artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r4</artifactId> <artifactId>hapi-fhir-structures-r4</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r5</artifactId> <artifactId>hapi-fhir-structures-r5</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu2</artifactId> <artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu3</artifactId> <artifactId>hapi-fhir-validation-resources-dstu3</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-r4</artifactId> <artifactId>hapi-fhir-validation-resources-r4</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.velocity</groupId> <groupId>org.apache.velocity</groupId>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<name>HAPI-FHIR</name> <name>HAPI-FHIR</name>
<description>An open-source implementation of the FHIR specification in Java.</description> <description>An open-source implementation of the FHIR specification in Java.</description>
<url>https://hapifhir.io</url> <url>https://hapifhir.io</url>

View File

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>5.5.0-PRE1-SNAPSHOT</version> <version>5.5.0-PRE2-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>