Merge branch 'master' into issue-2851-upload-terminology-valueset-parallel-versioning
This commit is contained in:
commit
9c8fd26692
|
@ -48,6 +48,7 @@ import java.util.IdentityHashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
@ -704,6 +705,21 @@ public class FhirTerser {
|
||||||
* @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
|
* @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
|
||||||
*/
|
*/
|
||||||
public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget) {
|
public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget) {
|
||||||
|
return isSourceInCompartmentForTarget(theCompartmentName, theSource, theTarget, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code>
|
||||||
|
* belonging to resource <code>theTarget</code>
|
||||||
|
*
|
||||||
|
* @param theCompartmentName The name of the compartment
|
||||||
|
* @param theSource The potential member of the compartment
|
||||||
|
* @param theTarget The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException}
|
||||||
|
* @param theAdditionalCompartmentParamNames If provided, search param names provided here will be considered as included in the given compartment for this comparison.
|
||||||
|
* @return <code>true</code> if <code>theSource</code> is in the compartment or one of the additional parameters matched.
|
||||||
|
* @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
|
||||||
|
*/
|
||||||
|
public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget, Set<String> theAdditionalCompartmentParamNames) {
|
||||||
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
|
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
|
||||||
Validate.notNull(theSource, "theSource must not be null");
|
Validate.notNull(theSource, "theSource must not be null");
|
||||||
Validate.notNull(theTarget, "theTarget must not be null");
|
Validate.notNull(theTarget, "theTarget must not be null");
|
||||||
|
@ -720,6 +736,20 @@ public class FhirTerser {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
|
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
|
||||||
|
|
||||||
|
//If passed an additional set of searchparameter names, add them for comparison purposes.
|
||||||
|
if (theAdditionalCompartmentParamNames != null) {
|
||||||
|
List<RuntimeSearchParam> additionalParams = theAdditionalCompartmentParamNames.stream().map(sourceDef::getSearchParam)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (params == null || params.isEmpty()) {
|
||||||
|
params = additionalParams;
|
||||||
|
} else {
|
||||||
|
params.addAll(additionalParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for (RuntimeSearchParam nextParam : params) {
|
for (RuntimeSearchParam nextParam : params) {
|
||||||
for (String nextPath : nextParam.getPathsSplit()) {
|
for (String nextPath : nextParam.getPathsSplit()) {
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ 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;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
@ -205,6 +206,19 @@ public class AuthorizationInterceptors {
|
||||||
};
|
};
|
||||||
//END SNIPPET: bulkExport
|
//END SNIPPET: bulkExport
|
||||||
|
|
||||||
|
//START SNIPPET: advancedCompartment
|
||||||
|
new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
AdditionalCompartmentSearchParameters additionalSearchParams = new AdditionalCompartmentSearchParameters();
|
||||||
|
additionalSearchParams.addSearchParameters("device:patient", "device:subject");
|
||||||
|
return new RuleBuilder()
|
||||||
|
.allow().read().allResources().inCompartmentWithAdditionalSearchParams("Patient", new IdType("Patient/123"), additionalSearchParams)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//END SNIPPET: advancedCompartment
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
jira: SMILE-1118
|
||||||
|
title: "Add new RuleBuilder options which allow you to specify additional resources and search parameters which match a given compartment. More explanations of
|
||||||
|
the enhancements can be found in [the documentation](/hapi-fhir/docs/security/authorization_interceptor.html#advanced-compartment-authorization)."
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
type: change
|
||||||
|
jira: SMILE-3128
|
||||||
|
title: "Prevent _expunge and _cascade from being used on the same DELETE operation"
|
|
@ -83,3 +83,14 @@ AuthorizationInterceptor can be used to provide nuanced control over the kinds o
|
||||||
```java
|
```java
|
||||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|bulkExport}}
|
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|bulkExport}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Advanced Compartment authorization
|
||||||
|
|
||||||
|
AuthorizationInterceptor can be used to provide fine-grained control over compartment reads and writes as well. There is a strict FHIR definition
|
||||||
|
of which resources and related search parameters fall into a given compartment. However, sometimes the defaults do not suffice. The following is an example
|
||||||
|
of an R4 ruleset which allows `device.patient` to be considered in the Patient compartment, on top of all the standard search parameters.
|
||||||
|
|
||||||
|
|
||||||
|
```java
|
||||||
|
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|advancedCompartment}}
|
||||||
|
```
|
||||||
|
|
|
@ -86,6 +86,7 @@ import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||||
|
@ -136,12 +137,9 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -588,6 +586,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
throw new MethodNotAllowedException("_expunge is not enabled on this server: " + getConfig().cannotDeleteExpungeReason());
|
throw new MethodNotAllowedException("_expunge is not enabled on this server: " + getConfig().cannotDeleteExpungeReason());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (theUrl.contains(Constants.PARAMETER_CASCADE_DELETE) || (theRequest.getHeader(Constants.HEADER_CASCADE) != null && theRequest.getHeader(Constants.HEADER_CASCADE).equals(Constants.CASCADE_DELETE))) {
|
||||||
|
throw new InvalidRequestException("_expunge cannot be used with _cascade");
|
||||||
|
}
|
||||||
|
|
||||||
List<String> urlsToDeleteExpunge = Collections.singletonList(theUrl);
|
List<String> urlsToDeleteExpunge = Collections.singletonList(theUrl);
|
||||||
try {
|
try {
|
||||||
JobExecution jobExecution = myDeleteExpungeJobSubmitter.submitJob(getConfig().getExpungeBatchSize(), urlsToDeleteExpunge, theRequest);
|
JobExecution jobExecution = myDeleteExpungeJobSubmitter.submitJob(getConfig().getExpungeBatchSize(), urlsToDeleteExpunge, theRequest);
|
||||||
|
|
|
@ -7,6 +7,8 @@ import ca.uhn.fhir.jpa.batch.writer.SqlExecutorWriter;
|
||||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||||
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
|
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
import ca.uhn.fhir.test.utilities.BatchJobHelper;
|
import ca.uhn.fhir.test.utilities.BatchJobHelper;
|
||||||
import ca.uhn.fhir.util.BundleBuilder;
|
import ca.uhn.fhir.util.BundleBuilder;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
@ -15,9 +17,11 @@ import org.hl7.fhir.r4.model.OperationOutcome;
|
||||||
import org.hl7.fhir.r4.model.Organization;
|
import org.hl7.fhir.r4.model.Organization;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.hl7.fhir.r4.model.Reference;
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
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.mockito.stubbing.Answer;
|
||||||
import org.springframework.batch.core.BatchStatus;
|
import org.springframework.batch.core.BatchStatus;
|
||||||
import org.springframework.batch.core.JobExecution;
|
import org.springframework.batch.core.JobExecution;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -27,6 +31,9 @@ import java.util.List;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.nullable;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
class DeleteExpungeDaoTest extends BaseJpaR4Test {
|
class DeleteExpungeDaoTest extends BaseJpaR4Test {
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -51,6 +58,41 @@ class DeleteExpungeDaoTest extends BaseJpaR4Test {
|
||||||
myDaoConfig.setExpungeBatchSize(defaultDaoConfig.getExpungeBatchSize());
|
myDaoConfig.setExpungeBatchSize(defaultDaoConfig.getExpungeBatchSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteCascadeExpungeReturns400() {
|
||||||
|
// Create new organization
|
||||||
|
Organization organization = new Organization();
|
||||||
|
organization.setName("FOO");
|
||||||
|
IIdType organizationId = myOrganizationDao.create(organization).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setManagingOrganization(new Reference(organizationId));
|
||||||
|
IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Try to delete _cascade and _expunge on the organization
|
||||||
|
BaseServerResponseException e = assertThrows(BaseServerResponseException.class, () -> {
|
||||||
|
myOrganizationDao
|
||||||
|
.deleteByUrl("Organization?" + "_cascade=delete&" + JpaConstants.PARAM_DELETE_EXPUNGE + "=true", mySrd);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get not implemented HTTP 400 error
|
||||||
|
assertEquals(Constants.STATUS_HTTP_400_BAD_REQUEST, e.getStatusCode());
|
||||||
|
assertEquals("_expunge cannot be used with _cascade", e.getMessage());
|
||||||
|
|
||||||
|
|
||||||
|
// Try to delete with header 'X-Cascade' = delete
|
||||||
|
when(mySrd.getHeader(Constants.HEADER_CASCADE)).thenReturn(Constants.CASCADE_DELETE);
|
||||||
|
e = assertThrows(BaseServerResponseException.class, () -> {
|
||||||
|
myOrganizationDao
|
||||||
|
.deleteByUrl("Organization?" + JpaConstants.PARAM_DELETE_EXPUNGE + "=true", mySrd);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get not implemented HTTP 400 error
|
||||||
|
assertEquals(Constants.STATUS_HTTP_400_BAD_REQUEST, e.getStatusCode());
|
||||||
|
assertEquals("_expunge cannot be used with _cascade", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteExpungeThrowExceptionIfForeignKeyLinksExists() {
|
public void testDeleteExpungeThrowExceptionIfForeignKeyLinksExists() {
|
||||||
// setup
|
// setup
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used in RuleBuilder, as a way to provide a compartment permission additional resource search params that
|
||||||
|
* are to be included as "in" the given compartment. For example, if you were to populate this map with
|
||||||
|
* [device -> ["patient", "subject"]
|
||||||
|
* and apply it to compartment Patient/123, then any device with Patient/123 as its patient would be considered "in"
|
||||||
|
* the compartment, despite the fact that device is technically not part of the compartment definition for patient.
|
||||||
|
*/
|
||||||
|
public class AdditionalCompartmentSearchParameters {
|
||||||
|
private Map<String, Set<String>> myResourceTypeToParameterCodeMap;
|
||||||
|
|
||||||
|
public AdditionalCompartmentSearchParameters() {
|
||||||
|
myResourceTypeToParameterCodeMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSearchParameters(@Nonnull String... theQualifiedSearchParameters) {
|
||||||
|
Arrays.stream(theQualifiedSearchParameters).forEach(code -> {
|
||||||
|
if (code == null || !code.contains(":")) {
|
||||||
|
throw new IllegalArgumentException(code + " is not a valid search parameter. Search parameters must be in the form resourcetype:parametercode, e.g. 'Device:patient'");
|
||||||
|
}
|
||||||
|
String[] split = code.split(":");
|
||||||
|
if (split.length != 2) {
|
||||||
|
throw new IllegalArgumentException(code + " is not a valid search parameter. Search parameters must be in the form resourcetype:parametercode, e.g. 'Device:patient'");
|
||||||
|
} else {
|
||||||
|
myResourceTypeToParameterCodeMap.computeIfAbsent(split[0].toLowerCase(), (key) -> new HashSet<>()).add(split[1].toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getSearchParamNamesForResourceType(@Nonnull String theResourceType) {
|
||||||
|
return myResourceTypeToParameterCodeMap.computeIfAbsent(theResourceType.toLowerCase(), (key) -> new HashSet<>());
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,4 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
enum AppliesTypeEnum {
|
enum AppliesTypeEnum {
|
||||||
ALL_RESOURCES, TYPES, INSTANCES
|
ALL_RESOURCES, TYPES, INSTANCES
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
@ -42,6 +43,30 @@ public interface IAuthRuleBuilderRuleOpClassifier {
|
||||||
*/
|
*/
|
||||||
IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, IIdType theOwner);
|
IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, IIdType theOwner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule applies to resources in the given compartment.
|
||||||
|
* <p>
|
||||||
|
* For example, to apply the rule to any observations in the patient compartment
|
||||||
|
* belonging to patient "123", you would invoke this with</br>
|
||||||
|
* <code>inCompartment("Patient", new IdType("Patient", "123"))</code>
|
||||||
|
*
|
||||||
|
* This call also allows you to pass additional search parameters that count as being included in the given compartment,
|
||||||
|
* passed in as a list of `resourceType:search-parameter-name`. For example, if you select a compartment name of "patient",
|
||||||
|
* you could pass in a singleton list consisting of the string "device:patient", which would cause any devices belonging
|
||||||
|
* to the patient to be permitted by the authorization rule.
|
||||||
|
*
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This call completes the rule and adds the rule to the chain.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param theCompartmentName The name of the compartment (must not be null or blank)
|
||||||
|
* @param theOwner The owner of the compartment. Note that both the resource type and ID must be populated in this ID.
|
||||||
|
* @param theAdditionalTypeSearchParamNames A list of strings for additional resource types and search parameters which count as being in the compartment, in the form "resourcetype:search-parameter-name".
|
||||||
|
*/
|
||||||
|
IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(String theCompartmentName, IIdType theOwner, AdditionalCompartmentSearchParameters theAdditionalTypeSearchParamNames);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rule applies to resources in the given compartment.
|
* Rule applies to resources in the given compartment.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -58,6 +83,32 @@ public interface IAuthRuleBuilderRuleOpClassifier {
|
||||||
*/
|
*/
|
||||||
IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, Collection<? extends IIdType> theOwners);
|
IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, Collection<? extends IIdType> theOwners);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule applies to resources in the given compartment.
|
||||||
|
* <p>
|
||||||
|
* For example, to apply the rule to any observations in the patient compartment
|
||||||
|
* belonging to patient "123", you would invoke this with</br>
|
||||||
|
* <code>inCompartment("Patient", new IdType("Patient", "123"))</code>
|
||||||
|
*
|
||||||
|
* This call also allows you to pass additional search parameters that count as being included in the given compartment,
|
||||||
|
* passed in as a list of `resourceType:search-parameter-name`. For example, if you select a compartment name of "patient",
|
||||||
|
* you could pass in a singleton list consisting of the string "device:patient", which would cause any devices belonging
|
||||||
|
* to the patient to be permitted by the authorization rule.
|
||||||
|
*
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This call completes the rule and adds the rule to the chain.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param theCompartmentName The name of the compartment (must not be null or blank)
|
||||||
|
* @param theOwners The owners of the compartment. Note that both the resource type and ID must be populated in these IDs.
|
||||||
|
* @param theAdditionalTypeSearchParamNames A {@link AdditionalCompartmentSearchParameters} which allows you to expand the search space for what is considered "in" the compartment.
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(String theCompartmentName, Collection<? extends IIdType> theOwners, AdditionalCompartmentSearchParameters theAdditionalTypeSearchParamNames);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rule applies to any resource instances
|
* Rule applies to any resource instances
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -451,6 +451,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
private Collection<? extends IIdType> myInCompartmentOwners;
|
private Collection<? extends IIdType> myInCompartmentOwners;
|
||||||
private Collection<IIdType> myAppliesToInstances;
|
private Collection<IIdType> myAppliesToInstances;
|
||||||
private RuleImplOp myRule;
|
private RuleImplOp myRule;
|
||||||
|
private AdditionalCompartmentSearchParameters myAdditionalSearchParamsForCompartmentTypes = new AdditionalCompartmentSearchParameters();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -483,6 +484,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
myRule.setClassifierCompartmentOwners(myInCompartmentOwners);
|
myRule.setClassifierCompartmentOwners(myInCompartmentOwners);
|
||||||
myRule.setAppliesToDeleteCascade(myOnCascade);
|
myRule.setAppliesToDeleteCascade(myOnCascade);
|
||||||
myRule.setAppliesToDeleteExpunge(myOnExpunge);
|
myRule.setAppliesToDeleteExpunge(myOnExpunge);
|
||||||
|
myRule.setAdditionalSearchParamsForCompartmentTypes(myAdditionalSearchParamsForCompartmentTypes);
|
||||||
myRules.add(myRule);
|
myRules.add(myRule);
|
||||||
|
|
||||||
return new RuleBuilderFinished(myRule);
|
return new RuleBuilderFinished(myRule);
|
||||||
|
@ -490,6 +492,11 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, Collection<? extends IIdType> theOwners) {
|
public IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, Collection<? extends IIdType> theOwners) {
|
||||||
|
return inCompartmentWithAdditionalSearchParams(theCompartmentName, theOwners, new AdditionalCompartmentSearchParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(String theCompartmentName, Collection<? extends IIdType> theOwners, AdditionalCompartmentSearchParameters theAdditionalTypeSearchParams) {
|
||||||
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
|
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
|
||||||
Validate.notNull(theOwners, "theOwners must not be null");
|
Validate.notNull(theOwners, "theOwners must not be null");
|
||||||
Validate.noNullElements(theOwners, "theOwners must not contain any null elements");
|
Validate.noNullElements(theOwners, "theOwners must not contain any null elements");
|
||||||
|
@ -498,20 +505,28 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
}
|
}
|
||||||
myInCompartmentName = theCompartmentName;
|
myInCompartmentName = theCompartmentName;
|
||||||
myInCompartmentOwners = theOwners;
|
myInCompartmentOwners = theOwners;
|
||||||
|
myAdditionalSearchParamsForCompartmentTypes = theAdditionalTypeSearchParams;
|
||||||
myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT;
|
myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT;
|
||||||
return finished();
|
return finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, IIdType theOwner) {
|
public IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, IIdType theOwner) {
|
||||||
|
return inCompartmentWithAdditionalSearchParams(theCompartmentName, theOwner, new AdditionalCompartmentSearchParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(String theCompartmentName, IIdType theOwner, AdditionalCompartmentSearchParameters theAdditionalTypeSearchParamNames) {
|
||||||
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
|
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
|
||||||
Validate.notNull(theOwner, "theOwner must not be null");
|
Validate.notNull(theOwner, "theOwner must not be null");
|
||||||
validateOwner(theOwner);
|
validateOwner(theOwner);
|
||||||
myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT;
|
myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT;
|
||||||
myInCompartmentName = theCompartmentName;
|
myInCompartmentName = theCompartmentName;
|
||||||
|
myAdditionalSearchParamsForCompartmentTypes = theAdditionalTypeSearchParamNames;
|
||||||
Optional<RuleImplOp> oRule = findMatchingRule();
|
Optional<RuleImplOp> oRule = findMatchingRule();
|
||||||
if (oRule.isPresent()) {
|
if (oRule.isPresent()) {
|
||||||
RuleImplOp rule = oRule.get();
|
RuleImplOp rule = oRule.get();
|
||||||
|
rule.setAdditionalSearchParamsForCompartmentTypes(myAdditionalSearchParamsForCompartmentTypes);
|
||||||
rule.addClassifierCompartmentOwner(theOwner);
|
rule.addClassifierCompartmentOwner(theOwner);
|
||||||
return new RuleBuilderFinished(rule);
|
return new RuleBuilderFinished(rule);
|
||||||
}
|
}
|
||||||
|
@ -519,6 +534,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
return finished();
|
return finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Optional<RuleImplOp> findMatchingRule() {
|
private Optional<RuleImplOp> findMatchingRule() {
|
||||||
return myRules.stream()
|
return myRules.stream()
|
||||||
.filter(RuleImplOp.class::isInstance)
|
.filter(RuleImplOp.class::isInstance)
|
||||||
|
|
|
@ -30,7 +30,9 @@ import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
@ -69,6 +71,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
private Collection<IIdType> myAppliesToInstances;
|
private Collection<IIdType> myAppliesToInstances;
|
||||||
private boolean myAppliesToDeleteCascade;
|
private boolean myAppliesToDeleteCascade;
|
||||||
private boolean myAppliesToDeleteExpunge;
|
private boolean myAppliesToDeleteExpunge;
|
||||||
|
private AdditionalCompartmentSearchParameters myAdditionalCompartmentSearchParamMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -337,7 +340,12 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
|
|
||||||
for (IIdType next : myClassifierCompartmentOwners) {
|
for (IIdType next : myClassifierCompartmentOwners) {
|
||||||
if (target.resource != null) {
|
if (target.resource != null) {
|
||||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, target.resource, next)) {
|
|
||||||
|
Set<String> additionalSearchParamNames = null;
|
||||||
|
if (myAdditionalCompartmentSearchParamMap != null) {
|
||||||
|
additionalSearchParamNames = myAdditionalCompartmentSearchParamMap.getSearchParamNamesForResourceType(ctx.getResourceType(target.resource));
|
||||||
|
}
|
||||||
|
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, target.resource, next, additionalSearchParamNames)) {
|
||||||
foundMatch = true;
|
foundMatch = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -371,7 +379,17 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
RuntimeResourceDefinition sourceDef = theRequestDetails.getFhirContext().getResourceDefinition(target.resourceType);
|
RuntimeResourceDefinition sourceDef = theRequestDetails.getFhirContext().getResourceDefinition(target.resourceType);
|
||||||
String compartmentOwnerResourceType = next.getResourceType();
|
String compartmentOwnerResourceType = next.getResourceType();
|
||||||
if (!StringUtils.equals(target.resourceType, compartmentOwnerResourceType)) {
|
if (!StringUtils.equals(target.resourceType, compartmentOwnerResourceType)) {
|
||||||
|
|
||||||
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(compartmentOwnerResourceType);
|
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(compartmentOwnerResourceType);
|
||||||
|
|
||||||
|
Set<String> additionalParamNames = myAdditionalCompartmentSearchParamMap.getSearchParamNamesForResourceType(sourceDef.getName());
|
||||||
|
List<RuntimeSearchParam> additionalParams = additionalParamNames.stream().map(sourceDef::getSearchParam).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
|
if (params == null || params.isEmpty()) {
|
||||||
|
params = additionalParams;
|
||||||
|
} else {
|
||||||
|
params.addAll(additionalParams);
|
||||||
|
}
|
||||||
|
|
||||||
if (!params.isEmpty()) {
|
if (!params.isEmpty()) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -681,4 +699,8 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAdditionalSearchParamsForCompartmentTypes(AdditionalCompartmentSearchParameters theAdditionalParameters) {
|
||||||
|
myAdditionalCompartmentSearchParamMap = theAdditionalParameters;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,8 @@ import java.util.concurrent.TimeUnit;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
@ -377,6 +379,160 @@ public class AuthorizationInterceptorR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomCompartmentSpsOnMultipleInstances() throws Exception {
|
||||||
|
//Given
|
||||||
|
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
AdditionalCompartmentSearchParameters additionalCompartmentSearchParameters = new AdditionalCompartmentSearchParameters();
|
||||||
|
additionalCompartmentSearchParameters.addSearchParameters("Device:patient");
|
||||||
|
List<IdType> relatedIds = new ArrayList<>();
|
||||||
|
relatedIds.add(new IdType("Patient/123"));
|
||||||
|
relatedIds.add(new IdType("Patient/456"));
|
||||||
|
return new RuleBuilder()
|
||||||
|
.allow().read().allResources()
|
||||||
|
.inCompartmentWithAdditionalSearchParams("Patient", relatedIds, additionalCompartmentSearchParameters)
|
||||||
|
.andThen().denyAll()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpGet httpGet;
|
||||||
|
HttpResponse status;
|
||||||
|
|
||||||
|
Patient patient;
|
||||||
|
patient = new Patient();
|
||||||
|
patient.setId("Patient/123");
|
||||||
|
Device d = new Device();
|
||||||
|
d.getPatient().setResource(patient);
|
||||||
|
|
||||||
|
ourHitMethod = false;
|
||||||
|
ourReturn = Collections.singletonList(d);
|
||||||
|
|
||||||
|
//When
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Device/124456");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
extractResponseAndClose(status);
|
||||||
|
|
||||||
|
//Then
|
||||||
|
assertTrue(ourHitMethod);
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomSearchParamsDontOverPermit() throws Exception {
|
||||||
|
//Given
|
||||||
|
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
AdditionalCompartmentSearchParameters additionalCompartmentSearchParameters = new AdditionalCompartmentSearchParameters();
|
||||||
|
additionalCompartmentSearchParameters.addSearchParameters("Encounter:patient");
|
||||||
|
List<IdType> relatedIds = new ArrayList<>();
|
||||||
|
relatedIds.add(new IdType("Patient/123"));
|
||||||
|
return new RuleBuilder()
|
||||||
|
.allow().read().allResources()
|
||||||
|
.inCompartmentWithAdditionalSearchParams("Patient", relatedIds, additionalCompartmentSearchParameters)
|
||||||
|
.andThen().denyAll()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpGet httpGet;
|
||||||
|
HttpResponse status;
|
||||||
|
|
||||||
|
Patient patient;
|
||||||
|
patient = new Patient();
|
||||||
|
patient.setId("Patient/123");
|
||||||
|
Device d = new Device();
|
||||||
|
d.getPatient().setResource(patient);
|
||||||
|
|
||||||
|
ourHitMethod = false;
|
||||||
|
ourReturn = Collections.singletonList(d);
|
||||||
|
|
||||||
|
//When
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Device/124456");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
extractResponseAndClose(status);
|
||||||
|
|
||||||
|
//then
|
||||||
|
assertFalse(ourHitMethod);
|
||||||
|
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonsenseParametersThrowAtRuntime() throws Exception {
|
||||||
|
//Given
|
||||||
|
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
AdditionalCompartmentSearchParameters additionalCompartmentSearchParameters = new AdditionalCompartmentSearchParameters();
|
||||||
|
additionalCompartmentSearchParameters.addSearchParameters("device:garbage");
|
||||||
|
List<IdType> relatedIds = new ArrayList<>();
|
||||||
|
relatedIds.add(new IdType("Patient/123"));
|
||||||
|
return new RuleBuilder()
|
||||||
|
.allow().read().allResources()
|
||||||
|
.inCompartmentWithAdditionalSearchParams("Patient", relatedIds, additionalCompartmentSearchParameters)
|
||||||
|
.andThen().denyAll()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpGet httpGet;
|
||||||
|
HttpResponse status;
|
||||||
|
|
||||||
|
Patient patient;
|
||||||
|
patient = new Patient();
|
||||||
|
patient.setId("Patient/123");
|
||||||
|
Device d = new Device();
|
||||||
|
d.getPatient().setResource(patient);
|
||||||
|
|
||||||
|
ourHitMethod = false;
|
||||||
|
ourReturn = Collections.singletonList(d);
|
||||||
|
|
||||||
|
//When
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Device/124456");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
extractResponseAndClose(status);
|
||||||
|
|
||||||
|
//then
|
||||||
|
assertFalse(ourHitMethod);
|
||||||
|
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRuleBuilderAdditionalSearchParamsInvalidValues() {
|
||||||
|
//Too many colons
|
||||||
|
try {
|
||||||
|
AdditionalCompartmentSearchParameters additionalCompartmentSearchParameters = new AdditionalCompartmentSearchParameters();
|
||||||
|
additionalCompartmentSearchParameters.addSearchParameters("too:many:colons");
|
||||||
|
new RuleBuilder()
|
||||||
|
.allow().read().allResources()
|
||||||
|
.inCompartmentWithAdditionalSearchParams("Patient", new IdType("Patient/123"), additionalCompartmentSearchParameters)
|
||||||
|
.andThen().denyAll()
|
||||||
|
.build();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertThat(e.getMessage(), is(equalTo("too:many:colons is not a valid search parameter. Search parameters must be in the form resourcetype:parametercode, e.g. 'Device:patient'")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//No colons
|
||||||
|
try {
|
||||||
|
AdditionalCompartmentSearchParameters additionalCompartmentSearchParameters = new AdditionalCompartmentSearchParameters();
|
||||||
|
additionalCompartmentSearchParameters.addSearchParameters("no-colons");
|
||||||
|
new RuleBuilder()
|
||||||
|
.allow().read().allResources()
|
||||||
|
.inCompartmentWithAdditionalSearchParams("Patient", new IdType("Patient/123"), additionalCompartmentSearchParameters)
|
||||||
|
.andThen().denyAll()
|
||||||
|
.build();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertThat(e.getMessage(), is(equalTo("no-colons is not a valid search parameter. Search parameters must be in the form resourcetype:parametercode, e.g. 'Device:patient'")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
|
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
|
||||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@ -437,6 +593,13 @@ public class AuthorizationInterceptorR4Test {
|
||||||
extractResponseAndClose(status);
|
extractResponseAndClose(status);
|
||||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||||
assertTrue(ourHitMethod);
|
assertTrue(ourHitMethod);
|
||||||
|
|
||||||
|
|
||||||
|
patient = new Patient();
|
||||||
|
patient.setId("Patient/123");
|
||||||
|
carePlan = new CarePlan();
|
||||||
|
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
|
||||||
|
carePlan.getSubject().setResource(patient);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3619,6 +3782,30 @@ public class AuthorizationInterceptorR4Test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DummyDeviceResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends IBaseResource> getResourceType() {
|
||||||
|
return Device.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Read(version = true)
|
||||||
|
public Device read(@IdParam IdType theId) {
|
||||||
|
ourHitMethod = true;
|
||||||
|
if (ourReturn.isEmpty()) {
|
||||||
|
throw new ResourceNotFoundException(theId);
|
||||||
|
}
|
||||||
|
return (Device) ourReturn.get(0);
|
||||||
|
}
|
||||||
|
@Search()
|
||||||
|
public List<Resource> search(
|
||||||
|
@OptionalParam(name = "patient") ReferenceParam thePatient
|
||||||
|
) {
|
||||||
|
ourHitMethod = true;
|
||||||
|
return ourReturn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static class DummyObservationResourceProvider implements IResourceProvider {
|
public static class DummyObservationResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
@ -3953,12 +4140,13 @@ public class AuthorizationInterceptorR4Test {
|
||||||
DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider();
|
DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider();
|
||||||
DummyCarePlanResourceProvider cpProv = new DummyCarePlanResourceProvider();
|
DummyCarePlanResourceProvider cpProv = new DummyCarePlanResourceProvider();
|
||||||
DummyDiagnosticReportResourceProvider drProv = new DummyDiagnosticReportResourceProvider();
|
DummyDiagnosticReportResourceProvider drProv = new DummyDiagnosticReportResourceProvider();
|
||||||
|
DummyDeviceResourceProvider devProv = new DummyDeviceResourceProvider();
|
||||||
PlainProvider plainProvider = new PlainProvider();
|
PlainProvider plainProvider = new PlainProvider();
|
||||||
|
|
||||||
ServletHandler proxyHandler = new ServletHandler();
|
ServletHandler proxyHandler = new ServletHandler();
|
||||||
ourServlet = new RestfulServer(ourCtx);
|
ourServlet = new RestfulServer(ourCtx);
|
||||||
ourServlet.setFhirContext(ourCtx);
|
ourServlet.setFhirContext(ourCtx);
|
||||||
ourServlet.registerProviders(patProvider, obsProv, encProv, cpProv, orgProv, drProv);
|
ourServlet.registerProviders(patProvider, obsProv, encProv, cpProv, orgProv, drProv, devProv);
|
||||||
ourServlet.registerProvider(new DummyServiceRequestResourceProvider());
|
ourServlet.registerProvider(new DummyServiceRequestResourceProvider());
|
||||||
ourServlet.registerProvider(new DummyConsentResourceProvider());
|
ourServlet.registerProvider(new DummyConsentResourceProvider());
|
||||||
ourServlet.setPlainProviders(plainProvider);
|
ourServlet.setPlainProviders(plainProvider);
|
||||||
|
|
Loading…
Reference in New Issue