Merge branch 'master' into subscription-bugfix
This commit is contained in:
commit
068117138e
|
@ -1,15 +1,11 @@
|
|||
package example;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
@ -17,6 +13,12 @@ import ca.uhn.fhir.rest.server.IResourceProvider;
|
|||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.*;
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AuthorizationInterceptors {
|
||||
|
@ -158,4 +160,47 @@ public class AuthorizationInterceptors {
|
|||
//END SNIPPET: patchAll
|
||||
|
||||
}
|
||||
|
||||
|
||||
//START SNIPPET: narrowing
|
||||
public class MyPatientSearchNarrowingInterceptor extends SearchNarrowingInterceptor {
|
||||
|
||||
/**
|
||||
* This method must be overridden to provide the list of compartments
|
||||
* and/or resources that the current user should have access to
|
||||
*/
|
||||
@Override
|
||||
protected AuthorizedList buildAuthorizedList(RequestDetails theRequestDetails) {
|
||||
// Process authorization header - The following is a fake
|
||||
// implementation. Obviously we'd want something more real
|
||||
// for a production scenario.
|
||||
//
|
||||
// In this basic example we have two hardcoded bearer tokens,
|
||||
// one which is for a user that has access to one patient, and
|
||||
// another that has full access.
|
||||
String authHeader = theRequestDetails.getHeader("Authorization");
|
||||
if ("Bearer dfw98h38r".equals(authHeader)) {
|
||||
|
||||
// This user will have access to two compartments
|
||||
return new AuthorizedList()
|
||||
.addCompartment("Patient/123")
|
||||
.addCompartment("Patient/456");
|
||||
|
||||
} else if ("Bearer 39ff939jgg".equals(authHeader)) {
|
||||
|
||||
// This user has access to everything
|
||||
return new AuthorizedList();
|
||||
|
||||
} else {
|
||||
|
||||
throw new AuthenticationException("Unknown bearer token");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//END SNIPPET: narrowing
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package ca.uhn.fhir.rest.gclient;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -98,6 +100,18 @@ public class ReferenceClientParam extends BaseClientParam implements IParam {
|
|||
return new StringCriterion<>(getParamName(), theIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the referenced resource if the resource has ANY of the given IDs
|
||||
* (this is an OR search, not an AND search), (this can be the logical ID or
|
||||
* the absolute URL of the resource). Note that to specify an AND search,
|
||||
* simply add a subsequent {@link IQuery#where(ICriterion) where} criteria
|
||||
* with the same parameter.
|
||||
*/
|
||||
public ICriterion<ReferenceClientParam> hasAnyOfIds(String... theIds) {
|
||||
Validate.notNull(theIds, "theIds must not be null");
|
||||
return hasAnyOfIds(Arrays.asList(theIds));
|
||||
}
|
||||
|
||||
private static class ReferenceChainCriterion implements ICriterion<ReferenceClientParam>, ICriterionInternal {
|
||||
|
||||
private final String myResourceTypeQualifier;
|
||||
|
|
|
@ -62,5 +62,10 @@ public abstract class BaseAndListParam<T extends IQueryParameterOr<?>> implement
|
|||
return myValues.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of AND parameters
|
||||
*/
|
||||
public int size() {
|
||||
return myValues.size();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,10 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -208,6 +210,13 @@ public class ParameterUtil {
|
|||
|| IPrimitiveType.class.isAssignableFrom(theClass);
|
||||
}
|
||||
|
||||
public static String escapeAndJoinOrList(Collection<String> theValues) {
|
||||
return theValues
|
||||
.stream()
|
||||
.map(ParameterUtil::escape)
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
public static int nonEscapedIndexOf(String theString, char theCharacter) {
|
||||
for (int i = 0; i < theString.length(); i++) {
|
||||
if (theString.charAt(i) == theCharacter) {
|
||||
|
|
|
@ -29,14 +29,14 @@ public interface IAnyResource extends IBaseResource {
|
|||
* Search parameter constant for <b>_language</b>
|
||||
*/
|
||||
@SearchParamDefinition(name="_language", path="", description="The language of the resource", type="string" )
|
||||
public static final String SP_RES_LANGUAGE = "_language";
|
||||
String SP_RES_LANGUAGE = "_language";
|
||||
|
||||
|
||||
/**
|
||||
* Search parameter constant for <b>_id</b>
|
||||
*/
|
||||
@SearchParamDefinition(name="_id", path="", description="The ID of the resource", type="token" )
|
||||
public static final String SP_RES_ID = "_id";
|
||||
String SP_RES_ID = "_id";
|
||||
|
||||
/**
|
||||
* <b>Fluent Client</b> search parameter constant for <b>_id</b>
|
||||
|
@ -46,7 +46,7 @@ public interface IAnyResource extends IBaseResource {
|
|||
* Path: <b>Resource._id</b><br>
|
||||
* </p>
|
||||
*/
|
||||
public static final TokenClientParam RES_ID = new TokenClientParam(IAnyResource.SP_RES_ID);
|
||||
TokenClientParam RES_ID = new TokenClientParam(IAnyResource.SP_RES_ID);
|
||||
|
||||
String getId();
|
||||
|
||||
|
@ -55,11 +55,11 @@ public interface IAnyResource extends IBaseResource {
|
|||
|
||||
IPrimitiveType<String> getLanguageElement();
|
||||
|
||||
public Object getUserData(String name);
|
||||
Object getUserData(String name);
|
||||
|
||||
@Override
|
||||
IAnyResource setId(String theId);
|
||||
|
||||
public void setUserData(String name, Object value);
|
||||
void setUserData(String name, Object value);
|
||||
|
||||
}
|
||||
|
|
|
@ -301,7 +301,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
|||
conformance = (IBaseResource) client.fetchConformance().ofType(implementingClass).execute();
|
||||
} catch (FhirClientConnectionException e) {
|
||||
if (!myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) && e.getCause() instanceof DataFormatException) {
|
||||
capabilityStatementResourceName = "Conformance";
|
||||
capabilityStatementResourceName = "CapabilityStatement";
|
||||
implementingClass = myContext.getResourceDefinition(capabilityStatementResourceName).getImplementingClass();
|
||||
conformance = (IBaseResource) client.fetchConformance().ofType(implementingClass).execute();
|
||||
} else {
|
||||
|
|
|
@ -81,7 +81,7 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
String paramName = isNotBlank(qualifier) ? getName() + qualifier : getName();
|
||||
List<String> paramValues = theTargetQueryArguments.get(paramName);
|
||||
if (paramValues == null) {
|
||||
paramValues = new ArrayList<String>(value.size());
|
||||
paramValues = new ArrayList<>(value.size());
|
||||
theTargetQueryArguments.put(paramName, paramValues);
|
||||
}
|
||||
|
||||
|
|
|
@ -233,7 +233,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
|
|||
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
|
||||
Entry nextRespEntry = response.getEntry().get(originalOrder);
|
||||
|
||||
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
|
||||
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails(theRequestDetails);
|
||||
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
|
||||
requestDetails.setRequestType(RequestTypeEnum.GET);
|
||||
requestDetails.setServer(theRequestDetails.getServer());
|
||||
|
|
|
@ -384,7 +384,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
|||
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
|
||||
BUNDLEENTRY nextRespEntry = myVersionAdapter.getEntries(response).get(originalOrder);
|
||||
|
||||
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
|
||||
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails(theRequestDetails);
|
||||
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
|
||||
requestDetails.setRequestType(RequestTypeEnum.GET);
|
||||
requestDetails.setServer(theRequestDetails.getServer());
|
||||
|
|
|
@ -29,11 +29,25 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
|||
|
||||
public class ServletSubRequestDetails extends ServletRequestDetails {
|
||||
|
||||
private Map<String, ArrayList<String>> myHeaders = new HashMap<>();
|
||||
private Map<String, List<String>> myHeaders = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theRequestDetails The parent request details
|
||||
*/
|
||||
public ServletSubRequestDetails(ServletRequestDetails theRequestDetails) {
|
||||
if (theRequestDetails != null) {
|
||||
Map<String, List<String>> headers = theRequestDetails.getHeaders();
|
||||
for (Map.Entry<String, List<String>> next : headers.entrySet()) {
|
||||
myHeaders.put(next.getKey().toLowerCase(), next.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addHeader(String theName, String theValue) {
|
||||
String lowerCase = theName.toLowerCase();
|
||||
ArrayList<String> list = myHeaders.get(lowerCase);
|
||||
List<String> list = myHeaders.get(lowerCase);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
myHeaders.put(lowerCase, list);
|
||||
|
@ -43,7 +57,7 @@ public class ServletSubRequestDetails extends ServletRequestDetails {
|
|||
|
||||
@Override
|
||||
public String getHeader(String theName) {
|
||||
ArrayList<String> list = myHeaders.get(theName.toLowerCase());
|
||||
List<String> list = myHeaders.get(theName.toLowerCase());
|
||||
if (list == null || list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -52,7 +66,7 @@ public class ServletSubRequestDetails extends ServletRequestDetails {
|
|||
|
||||
@Override
|
||||
public List<String> getHeaders(String theName) {
|
||||
ArrayList<String> list = myHeaders.get(theName.toLowerCase());
|
||||
List<String> list = myHeaders.get(theName.toLowerCase());
|
||||
if (list == null || list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,13 @@ public class SubscriptionInterceptorLoader {
|
|||
mySubscriptionMatcherInterceptor = myAppicationContext.getBean(SubscriptionMatcherInterceptor.class);
|
||||
}
|
||||
ourLog.info("Registering subscription matcher interceptor");
|
||||
|
||||
if (mySubscriptionMatcherInterceptor == null) {
|
||||
mySubscriptionMatcherInterceptor = myAppicationContext.getBean(SubscriptionMatcherInterceptor.class);
|
||||
}
|
||||
|
||||
myDaoConfig.registerInterceptor(mySubscriptionMatcherInterceptor);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,10 +42,7 @@ public class DaoSubscriptionProvider implements ISubscriptionProvider {
|
|||
@Override
|
||||
public IBundleProvider search(SearchParameterMap theMap) {
|
||||
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
return subscriptionDao.search(theMap, req);
|
||||
return subscriptionDao.search(theMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -72,12 +72,9 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher {
|
|||
RuntimeResourceDefinition responseResourceDef = subscriptionDao.validateCriteriaAndReturnResourceDefinition(theCriteria);
|
||||
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef);
|
||||
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
IFhirResourceDao<? extends IBaseResource> responseDao = myDaoRegistry.getResourceDao(responseResourceDef.getImplementingClass());
|
||||
responseCriteriaUrl.setLoadSynchronousUpTo(1);
|
||||
|
||||
return responseDao.search(responseCriteriaUrl, req);
|
||||
return responseDao.search(responseCriteriaUrl);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
|||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
|
@ -28,6 +30,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.in;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
@ -148,6 +151,58 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInTransaction() {
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
|
||||
patient.addName().setFamily("Tester").addGiven("Raghad");
|
||||
IIdType id = ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
String authHeader = theRequestDetails.getHeader("Authorization");
|
||||
if (!"Bearer AAA".equals(authHeader)) {
|
||||
throw new AuthenticationException("Invalid auth header: " + authHeader);
|
||||
}
|
||||
return new RuleBuilder()
|
||||
.allow().transaction().withAnyOperation().andApplyNormalRules().andThen()
|
||||
.allow().read().resourcesOfType(Patient.class).withAnyId()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
SimpleRequestHeaderInterceptor interceptor = new SimpleRequestHeaderInterceptor("Authorization", "Bearer AAA");
|
||||
try {
|
||||
ourClient.registerInterceptor(interceptor);
|
||||
|
||||
Bundle bundle;
|
||||
Bundle responseBundle;
|
||||
|
||||
// Read
|
||||
bundle = new Bundle();
|
||||
bundle.setType(Bundle.BundleType.TRANSACTION);
|
||||
bundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.GET).setUrl(id.getValue());
|
||||
responseBundle = ourClient.transaction().withBundle(bundle).execute();
|
||||
patient = (Patient) responseBundle.getEntry().get(0).getResource();
|
||||
assertEquals("Tester", patient.getNameFirstRep().getFamily());
|
||||
|
||||
// Search
|
||||
bundle = new Bundle();
|
||||
bundle.setType(Bundle.BundleType.TRANSACTION);
|
||||
bundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.GET).setUrl("Patient?");
|
||||
responseBundle = ourClient.transaction().withBundle(bundle).execute();
|
||||
responseBundle = (Bundle) responseBundle.getEntry().get(0).getResource();
|
||||
patient = (Patient) responseBundle.getEntry().get(0).getResource();
|
||||
assertEquals("Tester", patient.getNameFirstRep().getFamily());
|
||||
|
||||
} finally {
|
||||
ourClient.unregisterInterceptor(interceptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* See #751
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhirtest.config;
|
||||
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config;
|
|||
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
|
||||
|
@ -72,6 +73,11 @@ public class TdlDstu2Config extends BaseJavaConfigDstu2 {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ModelConfig modelConfig() {
|
||||
return daoConfig().getModelConfig();
|
||||
}
|
||||
|
||||
@Bean(name = "myPersistenceDataSourceDstu1", destroyMethod = "close")
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource retVal = new BasicDataSource();
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config;
|
|||
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||
|
@ -59,6 +60,11 @@ public class TdlDstu3Config extends BaseJavaConfigDstu3 {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ModelConfig modelConfig() {
|
||||
return daoConfig().getModelConfig();
|
||||
}
|
||||
|
||||
@Bean(name = "myPersistenceDataSourceDstu3", destroyMethod = "close")
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource retVal = new BasicDataSource();
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config;
|
|||
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
|
@ -71,6 +72,11 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ModelConfig modelConfig() {
|
||||
return daoConfig().getModelConfig();
|
||||
}
|
||||
|
||||
@Bean(name = "myPersistenceDataSourceDstu1", destroyMethod = "close")
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource retVal = new BasicDataSource();
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config;
|
|||
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||
|
@ -64,6 +65,12 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ModelConfig modelConfig() {
|
||||
return daoConfig().getModelConfig();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config;
|
|||
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigR4;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||
|
@ -63,6 +64,12 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ModelConfig modelConfig() {
|
||||
return daoConfig().getModelConfig();
|
||||
}
|
||||
|
||||
|
||||
@Bean(name = "myPersistenceDataSourceR4", destroyMethod = "close")
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource retVal = new BasicDataSource();
|
||||
|
|
|
@ -17,7 +17,7 @@ public class UhnFhirTestApp {
|
|||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
int myPort = 8888;
|
||||
int myPort = 8889;
|
||||
String base = "http://localhost:" + myPort + "/baseDstu2";
|
||||
|
||||
// new File("target/testdb").mkdirs();
|
||||
|
|
|
@ -59,6 +59,10 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
|||
* <a href="http://jamesagnew.github.io/hapi-fhir/doc_rest_server_security.html">Documentation on Server Security</a>
|
||||
* for information on how to use this interceptor.
|
||||
* </p>
|
||||
*
|
||||
* @see SearchNarrowingInterceptor
|
||||
*/
|
||||
public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter implements IRuleApplier {
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Return type for {@link SearchNarrowingInterceptor#buildAuthorizedList(RequestDetails)}
|
||||
*/
|
||||
public class AuthorizedList {
|
||||
|
||||
private List<String> myCompartments;
|
||||
private List<String> myResources;
|
||||
|
||||
List<String> getCompartments() {
|
||||
return myCompartments;
|
||||
}
|
||||
|
||||
List<String> getResources() {
|
||||
return myResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a compartment that the user should be allowed to access
|
||||
*
|
||||
* @param theCompartment The compartment name, e.g. "Patient/123" (in this example the user would be allowed to access Patient/123 as well as Observations where Observation.subject="Patient/123"m, etc.
|
||||
* @return Returns <code>this</code> for easy method chaining
|
||||
*/
|
||||
public AuthorizedList addCompartment(String theCompartment) {
|
||||
Validate.notNull(theCompartment, "theCompartment must not be null");
|
||||
if (myCompartments == null) {
|
||||
myCompartments = new ArrayList<>();
|
||||
}
|
||||
myCompartments.add(theCompartment);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a compartment that the user should be allowed to access
|
||||
*
|
||||
* @param theCompartments The compartment names, e.g. "Patient/123" (in this example the user would be allowed to access Patient/123 as well as Observations where Observation.subject="Patient/123"m, etc.
|
||||
* @return Returns <code>this</code> for easy method chaining
|
||||
*/
|
||||
public AuthorizedList addCompartments(String... theCompartments) {
|
||||
Validate.notNull(theCompartments, "theCompartments must not be null");
|
||||
for (String next : theCompartments) {
|
||||
addCompartment(next);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a resource that the user should be allowed to access
|
||||
*
|
||||
* @param theResource The resource name, e.g. "Patient/123" (in this example the user would be allowed to access Patient/123 but not Observations where Observation.subject="Patient/123"m, etc.
|
||||
* @return Returns <code>this</code> for easy method chaining
|
||||
*/
|
||||
public AuthorizedList addResource(String theResource) {
|
||||
Validate.notNull(theResource, "theResource must not be null");
|
||||
if (myResources == null) {
|
||||
myResources = new ArrayList<>();
|
||||
}
|
||||
myResources.add(theResource);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a resource that the user should be allowed to access
|
||||
*
|
||||
* @param theResources The resource names, e.g. "Patient/123" (in this example the user would be allowed to access Patient/123 but not Observations where Observation.subject="Patient/123"m, etc.
|
||||
* @return Returns <code>this</code> for easy method chaining
|
||||
*/
|
||||
public AuthorizedList addResources(String... theResources) {
|
||||
Validate.notNull(theResources, "theResources must not be null");
|
||||
for (String next : theResources) {
|
||||
addResource(next);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
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.interceptor.InterceptorAdapter;
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 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 abstract class SearchNarrowingInterceptor extends InterceptorAdapter {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected AuthorizedList buildAuthorizedList(@SuppressWarnings("unused") RequestDetails theRequestDetails) {
|
||||
return new AuthorizedList();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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);
|
||||
|
||||
/*
|
||||
* Create a map of search parameter values that need to be added to the
|
||||
* given request
|
||||
*/
|
||||
Collection<String> compartments = authorizedList.getCompartments();
|
||||
if (compartments != null) {
|
||||
processResourcesOrCompartments(theRequestDetails, resDef, parameterToOrValues, compartments, true);
|
||||
}
|
||||
Collection<String> resources = authorizedList.getResources();
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -34,9 +34,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -145,4 +143,18 @@ public class ServletRequestDetails extends RequestDetails {
|
|||
this.myServletResponse = myServletResponse;
|
||||
}
|
||||
|
||||
public Map<String,List<String>> getHeaders() {
|
||||
Map<String, List<String>> retVal = new HashMap<>();
|
||||
Enumeration<String> names = myServletRequest.getHeaderNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String nextName = names.nextElement();
|
||||
ArrayList<String> headerValues = new ArrayList<>();
|
||||
retVal.put(nextName, headerValues);
|
||||
Enumeration<String> valuesEnum = myServletRequest.getHeaders(nextName);
|
||||
while (valuesEnum.hasMoreElements()) {
|
||||
headerValues.add(valuesEnum.nextElement());
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(retVal);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -387,16 +387,24 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
|
|||
}
|
||||
}
|
||||
|
||||
RestResourceSearchParam param = resource.addSearchParam();
|
||||
String finalNextParamUnchainedName = nextParamUnchainedName;
|
||||
RestResourceSearchParam param =
|
||||
resource
|
||||
.getSearchParam()
|
||||
.stream()
|
||||
.filter(t -> t.getName().equals(finalNextParamUnchainedName))
|
||||
.findFirst()
|
||||
.orElseGet(() -> resource.addSearchParam());
|
||||
|
||||
param.setName(nextParamUnchainedName);
|
||||
if (StringUtils.isNotBlank(chain)) {
|
||||
param.addChain(chain);
|
||||
}
|
||||
|
||||
if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
|
||||
for (String nextWhitelist : new TreeSet<String>(nextParameter.getQualifierWhitelist())) {
|
||||
if (nextWhitelist.startsWith(".")) {
|
||||
param.addChain(nextWhitelist.substring(1));
|
||||
} else {
|
||||
if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
|
||||
for (String nextWhitelist : new TreeSet<String>(nextParameter.getQualifierWhitelist())) {
|
||||
if (nextWhitelist.startsWith(".")) {
|
||||
param.addChain(nextWhitelist.substring(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -579,6 +579,45 @@ public class ServerConformanceProviderDstu2Test {
|
|||
assertEquals(2, param.getChain().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchReferenceParameterWithExplicitChainsDocumentation() throws Exception {
|
||||
|
||||
RestfulServer rs = new RestfulServer(ourCtx);
|
||||
rs.setProviders(new SearchProviderWithExplicitChains());
|
||||
|
||||
ServerConformanceProvider sc = new ServerConformanceProvider(rs);
|
||||
rs.setServerConformanceProvider(sc);
|
||||
|
||||
rs.init(createServletConfig());
|
||||
|
||||
boolean found = false;
|
||||
Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
|
||||
for (ResourceBinding resourceBinding : resourceBindings) {
|
||||
if (resourceBinding.getResourceName().equals("Patient")) {
|
||||
List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
|
||||
SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
|
||||
SearchParameter param = (SearchParameter) binding.getParameters().get(0);
|
||||
assertEquals("The organization at which this person is a patient", param.getDescription());
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
assertTrue(found);
|
||||
Conformance conformance = sc.getServerConformance(createHttpServletRequest());
|
||||
|
||||
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
|
||||
ourLog.info(conf);
|
||||
|
||||
RestResource resource = findRestResource(conformance, "Patient");
|
||||
|
||||
assertEquals(1, resource.getSearchParam().size());
|
||||
RestResourceSearchParam param = resource.getSearchParam().get(0);
|
||||
assertEquals("organization", param.getName());
|
||||
assertEquals("bar", param.getChain().get(0).getValue());
|
||||
assertEquals("baz.bob", param.getChain().get(1).getValue());
|
||||
assertEquals("foo", param.getChain().get(2).getValue());
|
||||
assertEquals(3, param.getChain().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemHistorySupported() throws Exception {
|
||||
|
||||
|
@ -851,6 +890,19 @@ public class ServerConformanceProviderDstu2Test {
|
|||
|
||||
}
|
||||
|
||||
public static class SearchProviderWithExplicitChains {
|
||||
|
||||
@Search(type = Patient.class)
|
||||
public Patient findPatient1(
|
||||
@Description(shortDefinition = "The organization at which this person is a patient")
|
||||
@RequiredParam(name = "organization.foo") ReferenceAndListParam theFoo,
|
||||
@RequiredParam(name = "organization.bar") ReferenceAndListParam theBar,
|
||||
@RequiredParam(name = "organization.baz.bob") ReferenceAndListParam theBazbob) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SystemHistoryProvider {
|
||||
|
||||
@History
|
||||
|
|
|
@ -1,56 +1,50 @@
|
|||
package org.hl7.fhir.dstu3.hapi.rest.server;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.model.CapabilityStatement.*;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.model.api.annotation.Description;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.*;
|
||||
import ca.uhn.fhir.rest.server.method.*;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.ResourceBinding;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.RestulfulServerConfiguration;
|
||||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.IParameter;
|
||||
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.SearchParameter;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import ca.uhn.fhir.validation.FhirValidator;
|
||||
import ca.uhn.fhir.validation.ValidationResult;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.model.CapabilityStatement.*;
|
||||
import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
|
||||
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationDefinitionParameterComponent;
|
||||
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationKind;
|
||||
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationParameterUse;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ServerCapabilityStatementProviderDstu3Test {
|
||||
|
||||
private static FhirContext ourCtx;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProviderDstu3Test.class);
|
||||
private static FhirContext ourCtx;
|
||||
private static FhirValidator ourValidator;
|
||||
|
||||
static {
|
||||
|
@ -88,6 +82,47 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
return resource;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testSearchReferenceParameterWithExplicitChainsDocumentation() throws Exception {
|
||||
|
||||
RestfulServer rs = new RestfulServer(ourCtx);
|
||||
rs.setProviders(new SearchProviderWithExplicitChains());
|
||||
|
||||
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
|
||||
rs.setServerConformanceProvider(sc);
|
||||
|
||||
rs.init(createServletConfig());
|
||||
|
||||
boolean found = false;
|
||||
Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
|
||||
for (ResourceBinding resourceBinding : resourceBindings) {
|
||||
if (resourceBinding.getResourceName().equals("Patient")) {
|
||||
List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
|
||||
SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
|
||||
SearchParameter param = (SearchParameter) binding.getParameters().get(0);
|
||||
assertEquals("The organization at which this person is a patient", param.getDescription());
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
assertTrue(found);
|
||||
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest());
|
||||
|
||||
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
|
||||
ourLog.info(conf);
|
||||
|
||||
CapabilityStatementRestResourceComponent resource = findRestResource(conformance, "Patient");
|
||||
|
||||
assertEquals(1, resource.getSearchParam().size());
|
||||
CapabilityStatementRestResourceSearchParamComponent param = resource.getSearchParam().get(0);
|
||||
assertEquals("organization", param.getName());
|
||||
|
||||
// assertEquals("bar", param.getChain().get(0).getValue());
|
||||
// assertEquals("baz.bob", param.getChain().get(1).getValue());
|
||||
// assertEquals("foo", param.getChain().get(2).getValue());
|
||||
// assertEquals(3, param.getChain().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConditionalOperations() throws Exception {
|
||||
|
||||
|
@ -235,7 +270,9 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
assertNull(res.getConditionalUpdateElement().getValue());
|
||||
}
|
||||
|
||||
/** See #379 */
|
||||
/**
|
||||
* See #379
|
||||
*/
|
||||
@Test
|
||||
public void testOperationAcrossMultipleTypes() throws Exception {
|
||||
RestfulServer rs = new RestfulServer(ourCtx);
|
||||
|
@ -544,7 +581,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
@Test
|
||||
public void testSearchReferenceParameterWithList() throws Exception {
|
||||
|
||||
RestfulServer rsNoType = new RestfulServer(ourCtx){
|
||||
RestfulServer rsNoType = new RestfulServer(ourCtx) {
|
||||
@Override
|
||||
public RestulfulServerConfiguration createConfiguration() {
|
||||
RestulfulServerConfiguration retVal = super.createConfiguration();
|
||||
|
@ -561,7 +598,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
String confNoType = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
|
||||
ourLog.info(confNoType);
|
||||
|
||||
RestfulServer rsWithType = new RestfulServer(ourCtx){
|
||||
RestfulServer rsWithType = new RestfulServer(ourCtx) {
|
||||
@Override
|
||||
public RestulfulServerConfiguration createConfiguration() {
|
||||
RestulfulServerConfiguration retVal = super.createConfiguration();
|
||||
|
@ -720,8 +757,8 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
assertThat(param.getUse(), is(OperationParameterUse.IN));
|
||||
|
||||
CapabilityStatementRestResourceComponent patientResource = restComponent.getResource().stream()
|
||||
.filter(r -> patientResourceName.equals(r.getType()))
|
||||
.findAny().get();
|
||||
.filter(r -> patientResourceName.equals(r.getType()))
|
||||
.findAny().get();
|
||||
assertThat("Named query parameters should not appear in the resource search params", patientResource.getSearchParam(), is(empty()));
|
||||
}
|
||||
|
||||
|
@ -787,9 +824,17 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
assertTrue(outcome, result.isSuccessful());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
public static class SearchProviderWithExplicitChains {
|
||||
|
||||
@Search(type = Patient.class)
|
||||
public Patient findPatient1(
|
||||
@Description(shortDefinition = "The organization at which this person is a patient")
|
||||
@RequiredParam(name = "organization.foo") ReferenceAndListParam theFoo,
|
||||
@RequiredParam(name = "organization.bar") ReferenceAndListParam theBar,
|
||||
@RequiredParam(name = "organization.baz.bob") ReferenceAndListParam theBazbob) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -836,7 +881,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
|
||||
@Search(type = Patient.class)
|
||||
public Patient findPatient(@Description(shortDefinition = "The patient's identifier") @OptionalParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier,
|
||||
@Description(shortDefinition = "The patient's name") @OptionalParam(name = Patient.SP_NAME) StringParam theName) {
|
||||
@Description(shortDefinition = "The patient's name") @OptionalParam(name = Patient.SP_NAME) StringParam theName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -847,7 +892,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
|
||||
@Operation(name = "someOp")
|
||||
public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam IdType theId,
|
||||
@OperationParam(name = "someOpParam1") DateType theStart, @OperationParam(name = "someOpParam2") Encounter theEnd) {
|
||||
@OperationParam(name = "someOpParam1") DateType theStart, @OperationParam(name = "someOpParam2") Encounter theEnd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -868,7 +913,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
|
||||
@Operation(name = "someOp")
|
||||
public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam IdType theId,
|
||||
@OperationParam(name = "someOpParam1") DateType theStart, @OperationParam(name = "someOpParam2") Patient theEnd) {
|
||||
@OperationParam(name = "someOpParam1") DateType theStart, @OperationParam(name = "someOpParam2") Patient theEnd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -912,9 +957,9 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
@SuppressWarnings("unused")
|
||||
public static class PlainProviderWithExtendedOperationOnNoType {
|
||||
|
||||
@Operation(name = "plain", idempotent = true, returnParameters = { @OperationParam(min = 1, max = 2, name = "out1", type = StringType.class) })
|
||||
@Operation(name = "plain", idempotent = true, returnParameters = {@OperationParam(min = 1, max = 2, name = "out1", type = StringType.class)})
|
||||
public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam IdType theId, @OperationParam(name = "start") DateType theStart,
|
||||
@OperationParam(name = "end") DateType theEnd) {
|
||||
@OperationParam(name = "end") DateType theEnd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -925,7 +970,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
|
||||
@Operation(name = "everything", idempotent = true)
|
||||
public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam IdType theId, @OperationParam(name = "start") DateType theStart,
|
||||
@OperationParam(name = "end") DateType theEnd) {
|
||||
@OperationParam(name = "end") DateType theEnd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -942,8 +987,8 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
@Description(shortDefinition = "This is a search for stuff!")
|
||||
@Search
|
||||
public List<DiagnosticReport> findDiagnosticReportsByPatient(@RequiredParam(name = DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) TokenParam thePatientId,
|
||||
@OptionalParam(name = DiagnosticReport.SP_CODE) TokenOrListParam theNames, @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange,
|
||||
@IncludeParam(allow = { "DiagnosticReport.result" }) Set<Include> theIncludes) throws Exception {
|
||||
@OptionalParam(name = DiagnosticReport.SP_CODE) TokenOrListParam theNames, @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange,
|
||||
@IncludeParam(allow = {"DiagnosticReport.result"}) Set<Include> theIncludes) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -974,7 +1019,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
|
||||
@Search(type = Patient.class)
|
||||
public Patient findPatient2(
|
||||
@Description(shortDefinition = "All patients linked to the given patient") @OptionalParam(name = "link", targetTypes = { Patient.class }) ReferenceAndListParam theLink) {
|
||||
@Description(shortDefinition = "All patients linked to the given patient") @OptionalParam(name = "link", targetTypes = {Patient.class}) ReferenceAndListParam theLink) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -984,15 +1029,15 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
public static class SearchProviderWithWhitelist {
|
||||
|
||||
@Search(type = Patient.class)
|
||||
public Patient findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION, chainWhitelist = { "foo",
|
||||
"bar" }) ReferenceAndListParam theIdentifier) {
|
||||
public Patient findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION, chainWhitelist = {"foo",
|
||||
"bar"}) ReferenceAndListParam theIdentifier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class SearchProviderWithListNoType implements IResourceProvider {
|
||||
public static class SearchProviderWithListNoType implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
|
@ -1000,7 +1045,6 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
}
|
||||
|
||||
|
||||
|
||||
@Search()
|
||||
public List<Patient> findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION) ReferenceAndListParam theIdentifier) {
|
||||
return null;
|
||||
|
@ -1009,7 +1053,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class SearchProviderWithListWithType implements IResourceProvider {
|
||||
public static class SearchProviderWithListWithType implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
|
@ -1017,15 +1061,13 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
}
|
||||
|
||||
|
||||
|
||||
@Search(type=Patient.class)
|
||||
@Search(type = Patient.class)
|
||||
public List<Patient> findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION) ReferenceAndListParam theIdentifier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class SystemHistoryProvider {
|
||||
|
||||
@History
|
||||
|
@ -1110,4 +1152,9 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,16 +1,19 @@
|
|||
package ca.uhn.fhir.rest.client;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.annotation.IncludeParam;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.ReaderInputStream;
|
||||
import org.apache.http.HttpResponse;
|
||||
|
@ -23,118 +26,144 @@ import org.apache.http.message.BasicStatusLine;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.Encounter;
|
||||
import org.junit.*;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class SearchClientTest {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchClientTest.class);
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchClientTest.class);
|
||||
|
||||
private FhirContext ourCtx;
|
||||
private HttpClient ourHttpClient;
|
||||
private HttpResponse ourHttpResponse;
|
||||
private FhirContext ourCtx;
|
||||
private HttpClient ourHttpClient;
|
||||
private HttpResponse ourHttpResponse;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourCtx = FhirContext.forR4();
|
||||
@Before
|
||||
public void before() {
|
||||
ourCtx = FhirContext.forR4();
|
||||
|
||||
ourHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
|
||||
ourCtx.getRestfulClientFactory().setHttpClient(ourHttpClient);
|
||||
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
|
||||
ourHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
|
||||
ourCtx.getRestfulClientFactory().setHttpClient(ourHttpClient);
|
||||
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
|
||||
|
||||
ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
|
||||
}
|
||||
ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostOnLongParamsList() throws Exception {
|
||||
String resp = createBundle();
|
||||
@Test
|
||||
public void testPostOnLongParamsList() throws Exception {
|
||||
String resp = createBundle();
|
||||
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
||||
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML_NEW + "; charset=UTF-8"));
|
||||
when(ourHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(resp), Charset.forName("UTF-8")));
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
||||
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML_NEW + "; charset=UTF-8"));
|
||||
when(ourHttpResponse.getEntity().getContent()).thenAnswer(t->new ReaderInputStream(new StringReader(resp), Charset.forName("UTF-8")));
|
||||
|
||||
ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo");
|
||||
Set<Include> includes = new HashSet<Include>();
|
||||
includes.add(new Include("one"));
|
||||
includes.add(new Include("two"));
|
||||
TokenOrListParam params = new TokenOrListParam();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
params.add(new TokenParam("system", "value"));
|
||||
}
|
||||
List<Encounter> found = client.searchByList(params, includes);
|
||||
ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo");
|
||||
Set<Include> includes = new HashSet<Include>();
|
||||
includes.add(new Include("one"));
|
||||
includes.add(new Include("two"));
|
||||
TokenOrListParam params = new TokenOrListParam();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
params.add(new TokenParam("system", "value"));
|
||||
}
|
||||
|
||||
assertEquals(1, found.size());
|
||||
// With OR list
|
||||
|
||||
Encounter encounter = found.get(0);
|
||||
assertNotNull(encounter.getSubject().getReference());
|
||||
HttpUriRequest value = capt.getValue();
|
||||
List<Encounter> found = client.searchByList(params, includes);
|
||||
|
||||
assertTrue("Expected request of type POST on long params list", value instanceof HttpPost);
|
||||
HttpPost post = (HttpPost) value;
|
||||
String body = IOUtils.toString(post.getEntity().getContent());
|
||||
ourLog.info(body);
|
||||
assertThat(body, Matchers.containsString("_include=one"));
|
||||
assertThat(body, Matchers.containsString("_include=two"));
|
||||
}
|
||||
assertEquals(1, found.size());
|
||||
|
||||
@Test
|
||||
public void testReturnTypedList() throws Exception {
|
||||
Encounter encounter = found.get(0);
|
||||
assertNotNull(encounter.getSubject().getReference());
|
||||
HttpUriRequest value = capt.getValue();
|
||||
|
||||
String resp = createBundle();
|
||||
assertTrue("Expected request of type POST on long params list", value instanceof HttpPost);
|
||||
HttpPost post = (HttpPost) value;
|
||||
String body = IOUtils.toString(post.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(body);
|
||||
assertThat(body, Matchers.containsString("_include=one"));
|
||||
assertThat(body, Matchers.containsString("_include=two"));
|
||||
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
||||
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML_NEW + "; charset=UTF-8"));
|
||||
when(ourHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(resp), Charset.forName("UTF-8")));
|
||||
// With AND list
|
||||
|
||||
ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo");
|
||||
List<Encounter> found = client.search();
|
||||
assertEquals(1, found.size());
|
||||
TokenAndListParam paramsAndList = new TokenAndListParam();
|
||||
paramsAndList.addAnd(params);
|
||||
found = client.searchByList(paramsAndList, includes);
|
||||
|
||||
Encounter encounter = found.get(0);
|
||||
assertNotNull(encounter.getSubject().getReference());
|
||||
}
|
||||
assertEquals(1, found.size());
|
||||
|
||||
private String createBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
encounter = found.get(0);
|
||||
assertNotNull(encounter.getSubject().getReference());
|
||||
value = capt.getAllValues().get(1);
|
||||
|
||||
Encounter enc = new Encounter();
|
||||
enc.getSubject().setReference("Patient/1");
|
||||
assertTrue("Expected request of type POST on long params list", value instanceof HttpPost);
|
||||
post = (HttpPost) value;
|
||||
body = IOUtils.toString(post.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(body);
|
||||
assertThat(body, Matchers.containsString("_include=one"));
|
||||
assertThat(body, Matchers.containsString("_include=two"));
|
||||
}
|
||||
|
||||
bundle.addEntry().setResource(enc);
|
||||
@Test
|
||||
public void testReturnTypedList() throws Exception {
|
||||
|
||||
String retVal = ourCtx.newXmlParser().encodeResourceToString(bundle);
|
||||
return retVal;
|
||||
}
|
||||
String resp = createBundle();
|
||||
|
||||
private interface ITestClient extends IBasicClient {
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
||||
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML_NEW + "; charset=UTF-8"));
|
||||
when(ourHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(resp), Charset.forName("UTF-8")));
|
||||
|
||||
@Search
|
||||
List<Encounter> search();
|
||||
ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo");
|
||||
List<Encounter> found = client.search();
|
||||
assertEquals(1, found.size());
|
||||
|
||||
@Search
|
||||
List<Encounter> searchByList(@RequiredParam(name = Encounter.SP_IDENTIFIER) TokenOrListParam tokenOrListParam, @IncludeParam Set<Include> theIncludes) throws BaseServerResponseException;
|
||||
Encounter encounter = found.get(0);
|
||||
assertNotNull(encounter.getSubject().getReference());
|
||||
}
|
||||
|
||||
}
|
||||
private String createBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
Encounter enc = new Encounter();
|
||||
enc.getSubject().setReference("Patient/1");
|
||||
|
||||
bundle.addEntry().setResource(enc);
|
||||
|
||||
String retVal = ourCtx.newXmlParser().encodeResourceToString(bundle);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private interface ITestClient extends IBasicClient {
|
||||
|
||||
@Search
|
||||
List<Encounter> search();
|
||||
|
||||
@Search
|
||||
List<Encounter> searchByList(@RequiredParam(name = Encounter.SP_IDENTIFIER) TokenOrListParam tokenOrListParam, @IncludeParam Set<Include> theIncludes) throws BaseServerResponseException;
|
||||
|
||||
@Search
|
||||
List<Encounter> searchByList(@RequiredParam(name = Encounter.SP_IDENTIFIER) TokenAndListParam tokenOrListParam, @IncludeParam Set<Include> theIncludes) throws BaseServerResponseException;
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.param.BaseAndListParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
|
||||
import ca.uhn.fhir.rest.param.StringAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class SearchNarrowingInterceptorTest {
|
||||
|
||||
private static String ourLastHitMethod;
|
||||
private static FhirContext ourCtx;
|
||||
private static TokenAndListParam ourLastIdParam;
|
||||
private static TokenAndListParam ourLastCodeParam;
|
||||
private static ReferenceAndListParam ourLastSubjectParam;
|
||||
private static ReferenceAndListParam ourLastPatientParam;
|
||||
private static ReferenceAndListParam ourLastPerformerParam;
|
||||
private static StringAndListParam ourLastNameParam;
|
||||
private static List<Resource> ourReturn;
|
||||
private static Server ourServer;
|
||||
private static IGenericClient ourClient;
|
||||
private static AuthorizedList ourNextCompartmentList;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourLastHitMethod = null;
|
||||
ourReturn = Collections.emptyList();
|
||||
ourLastIdParam = null;
|
||||
ourLastNameParam = null;
|
||||
ourLastSubjectParam = null;
|
||||
ourLastPatientParam = null;
|
||||
ourLastPerformerParam = null;
|
||||
ourLastCodeParam = null;
|
||||
ourNextCompartmentList = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNarrowObservationsByPatientContext_ClientRequestedNoParams() {
|
||||
|
||||
ourNextCompartmentList = new AuthorizedList().addCompartments("Patient/123", "Patient/456");
|
||||
|
||||
ourClient
|
||||
.search()
|
||||
.forResource("Observation")
|
||||
.execute();
|
||||
|
||||
assertEquals("Observation.search", ourLastHitMethod);
|
||||
assertNull(ourLastIdParam);
|
||||
assertNull(ourLastCodeParam);
|
||||
assertNull(ourLastSubjectParam);
|
||||
assertNull(ourLastPerformerParam);
|
||||
assertThat(toStrings(ourLastPatientParam), Matchers.contains("Patient/123,Patient/456"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Should not make any changes
|
||||
*/
|
||||
@Test
|
||||
public void testNarrowObservationsByPatientResources_ClientRequestedNoParams() {
|
||||
|
||||
ourNextCompartmentList = new AuthorizedList().addResources("Patient/123", "Patient/456");
|
||||
|
||||
ourClient
|
||||
.search()
|
||||
.forResource("Observation")
|
||||
.execute();
|
||||
|
||||
assertEquals("Observation.search", ourLastHitMethod);
|
||||
assertNull(ourLastIdParam);
|
||||
assertNull(ourLastCodeParam);
|
||||
assertNull(ourLastSubjectParam);
|
||||
assertNull(ourLastPerformerParam);
|
||||
assertNull(ourLastPatientParam);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNarrowPatientByPatientResources_ClientRequestedNoParams() {
|
||||
|
||||
ourNextCompartmentList = new AuthorizedList().addResources("Patient/123", "Patient/456");
|
||||
|
||||
ourClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.execute();
|
||||
|
||||
assertEquals("Patient.search", ourLastHitMethod);
|
||||
assertNull(ourLastCodeParam);
|
||||
assertNull(ourLastSubjectParam);
|
||||
assertNull(ourLastPerformerParam);
|
||||
assertNull(ourLastPatientParam);
|
||||
assertThat(toStrings(ourLastIdParam), Matchers.contains("Patient/123,Patient/456"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNarrowPatientByPatientContext_ClientRequestedNoParams() {
|
||||
|
||||
ourNextCompartmentList = new AuthorizedList().addCompartments("Patient/123", "Patient/456");
|
||||
|
||||
ourClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.execute();
|
||||
|
||||
assertEquals("Patient.search", ourLastHitMethod);
|
||||
assertNull(ourLastNameParam);
|
||||
assertThat(toStrings(ourLastIdParam), Matchers.contains("Patient/123,Patient/456"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNarrowPatientByPatientContext_ClientRequestedSomeOverlap() {
|
||||
|
||||
ourNextCompartmentList = new AuthorizedList().addCompartments("Patient/123", "Patient/456");
|
||||
|
||||
ourClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.where(IAnyResource.RES_ID.exactly().codes("Patient/123", "Patient/999"))
|
||||
.execute();
|
||||
|
||||
assertEquals("Patient.search", ourLastHitMethod);
|
||||
assertNull(ourLastNameParam);
|
||||
assertThat(toStrings(ourLastIdParam), Matchers.contains("Patient/123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNarrowObservationsByPatientContext_ClientRequestedSomeOverlap() {
|
||||
|
||||
ourNextCompartmentList = new AuthorizedList().addCompartments("Patient/123", "Patient/456");
|
||||
|
||||
ourClient
|
||||
.search()
|
||||
.forResource("Observation")
|
||||
.where(Observation.PATIENT.hasAnyOfIds("Patient/456", "Patient/777"))
|
||||
.and(Observation.PATIENT.hasAnyOfIds("Patient/456", "Patient/888"))
|
||||
.execute();
|
||||
|
||||
assertEquals("Observation.search", ourLastHitMethod);
|
||||
assertNull(ourLastIdParam);
|
||||
assertNull(ourLastCodeParam);
|
||||
assertNull(ourLastSubjectParam);
|
||||
assertNull(ourLastPerformerParam);
|
||||
assertThat(toStrings(ourLastPatientParam), Matchers.contains("Patient/456", "Patient/456"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNarrowObservationsByPatientContext_ClientRequestedNoOverlap() {
|
||||
|
||||
ourNextCompartmentList = new AuthorizedList().addCompartments("Patient/123", "Patient/456");
|
||||
|
||||
ourClient
|
||||
.search()
|
||||
.forResource("Observation")
|
||||
.where(Observation.PATIENT.hasAnyOfIds("Patient/111", "Patient/777"))
|
||||
.and(Observation.PATIENT.hasAnyOfIds("Patient/111", "Patient/888"))
|
||||
.execute();
|
||||
|
||||
assertEquals("Observation.search", ourLastHitMethod);
|
||||
assertNull(ourLastIdParam);
|
||||
assertNull(ourLastCodeParam);
|
||||
assertNull(ourLastSubjectParam);
|
||||
assertNull(ourLastPerformerParam);
|
||||
assertThat(toStrings(ourLastPatientParam), Matchers.contains("Patient/111,Patient/777", "Patient/111,Patient/888", "Patient/123,Patient/456"));
|
||||
}
|
||||
|
||||
private List<String> toStrings(BaseAndListParam<? extends IQueryParameterOr<?>> theParams) {
|
||||
List<? extends IQueryParameterOr<? extends IQueryParameterType>> valuesAsQueryTokens = theParams.getValuesAsQueryTokens();
|
||||
|
||||
return valuesAsQueryTokens
|
||||
.stream()
|
||||
.map(IQueryParameterOr::getValuesAsQueryTokens)
|
||||
.map(t -> t
|
||||
.stream()
|
||||
.map(j -> j.getValueAsQueryToken(ourCtx))
|
||||
.collect(Collectors.joining(",")))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Resource> search(
|
||||
@OptionalParam(name = "_id") TokenAndListParam theIdParam,
|
||||
@OptionalParam(name = "name") StringAndListParam theNameParam
|
||||
) {
|
||||
ourLastHitMethod = "Patient.search";
|
||||
ourLastIdParam = theIdParam;
|
||||
ourLastNameParam = theNameParam;
|
||||
return ourReturn;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class DummyObservationResourceProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return Observation.class;
|
||||
}
|
||||
|
||||
|
||||
@Search()
|
||||
public List<Resource> search(
|
||||
@OptionalParam(name = "_id") TokenAndListParam theIdParam,
|
||||
@OptionalParam(name = Observation.SP_SUBJECT) ReferenceAndListParam theSubjectParam,
|
||||
@OptionalParam(name = Observation.SP_PATIENT) ReferenceAndListParam thePatientParam,
|
||||
@OptionalParam(name = Observation.SP_PERFORMER) ReferenceAndListParam thePerformerParam,
|
||||
@OptionalParam(name = "code") TokenAndListParam theCodeParam
|
||||
) {
|
||||
ourLastHitMethod = "Observation.search";
|
||||
ourLastIdParam = theIdParam;
|
||||
ourLastSubjectParam = theSubjectParam;
|
||||
ourLastPatientParam = thePatientParam;
|
||||
ourLastPerformerParam = thePerformerParam;
|
||||
ourLastCodeParam = theCodeParam;
|
||||
return ourReturn;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MySearchNarrowingInterceptor extends SearchNarrowingInterceptor {
|
||||
@Override
|
||||
protected AuthorizedList buildAuthorizedList(RequestDetails theRequestDetails) {
|
||||
Validate.notNull(ourNextCompartmentList);
|
||||
return ourNextCompartmentList;
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourCtx = FhirContext.forR4();
|
||||
|
||||
int ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
DummyPatientResourceProvider patProvider = new DummyPatientResourceProvider();
|
||||
DummyObservationResourceProvider obsProv = new DummyObservationResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer ourServlet = new RestfulServer(ourCtx);
|
||||
ourServlet.setFhirContext(ourCtx);
|
||||
ourServlet.setResourceProviders(patProvider, obsProv);
|
||||
ourServlet.setPagingProvider(new FifoMemoryPagingProvider(100));
|
||||
ourServlet.registerInterceptor(new MySearchNarrowingInterceptor());
|
||||
ServletHolder servletHolder = new ServletHolder(ourServlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
ourServer.start();
|
||||
|
||||
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
|
||||
ourCtx.getRestfulClientFactory().setSocketTimeout(1000000);
|
||||
ourClient = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
|
||||
}
|
||||
|
||||
|
||||
}
|
8
pom.xml
8
pom.xml
|
@ -519,6 +519,7 @@
|
|||
<commons_lang3_version>3.8.1</commons_lang3_version>
|
||||
<derby_version>10.14.2.0</derby_version>
|
||||
<error_prone_annotations_version>2.0.18</error_prone_annotations_version>
|
||||
<error_prone_core_version>2.3.2</error_prone_core_version>
|
||||
<guava_version>25.0-jre</guava_version>
|
||||
<gson_version>2.8.5</gson_version>
|
||||
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
|
||||
|
@ -639,7 +640,7 @@
|
|||
<dependency>
|
||||
<groupId>com.google.errorprone</groupId>
|
||||
<artifactId>error_prone_core</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>${error_prone_core_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
@ -766,7 +767,8 @@
|
|||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>6.2.2.jre8</version>
|
||||
<!--<version>6.2.2.jre8</version>-->
|
||||
<version>7.0.0.jre8</version>
|
||||
</dependency>
|
||||
<!--
|
||||
<dependency>
|
||||
|
@ -2308,7 +2310,7 @@
|
|||
<path>
|
||||
<groupId>com.google.errorprone</groupId>
|
||||
<artifactId>error_prone_core</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>${error_prone_core_version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
|
|
|
@ -264,6 +264,31 @@
|
|||
OperationDefinitions are now created for named queries in server
|
||||
module. Thanks to Stig Døssing for the pull request!
|
||||
</action>
|
||||
<action type="add">
|
||||
A new server interceptor has been added called "SearchNarrowingInterceptor".
|
||||
This interceptor can be used to automatically narrow the scope of searches
|
||||
performed by the user to limit them to specific resources or compartments
|
||||
that the user should have access to.
|
||||
</action>
|
||||
<action type="add">
|
||||
In a DSTU2 server, if search parameters are expressed with chains directly in the
|
||||
parameter name (e.g.
|
||||
<![CDATA[<code>@RequiredParam(name="subject.name.family")</code>]]>) the second
|
||||
part of the chain was lost when the chain was described in the server
|
||||
CapabilityStatement. This has been corrected.
|
||||
</action>
|
||||
<action type="fix">
|
||||
In the JPA server, search/read operations being performed within a transaction bundle
|
||||
did not pass the client request HTTP headers to the sub-request. This meant that
|
||||
AuthorizationInterceptor could not authorize these requests if it was depending on
|
||||
headers being present.
|
||||
</action>
|
||||
<action type="fix">
|
||||
When using a client in DSTU3/R4 mode, if the client attempted to validate the server
|
||||
CapabilityStatement but was not able to parse the response, the client would throw
|
||||
an exception with a misleading error about the Conformance resource not existing. This
|
||||
has been corrected. Thanks to Shayaan Munshi for reporting and providing a test case!
|
||||
</action>
|
||||
</release>
|
||||
<release version="3.6.0" date="2018-11-12" description="Food">
|
||||
<action type="add">
|
||||
|
|
|
@ -96,10 +96,10 @@
|
|||
</p>
|
||||
|
||||
<p class="doc_info_bubble">
|
||||
AuthorizationInterceptor is a new feature in HAPI FHIR, and has not yet
|
||||
been heavily tested. Use with caution, and do lots of testing! We welcome
|
||||
feedback and suggestions on this feature. In addition, this documentation is
|
||||
not yet complete. More examples and details will be added soon! Please get in
|
||||
AuthorizationInterceptor has been well tested, but it is impossible to
|
||||
predeict every scenario and environment in which HAPI FHIR will be used.
|
||||
Use with caution, and do lots of testing! We welcome
|
||||
feedback and suggestions on this feature. Please get in
|
||||
touch if you'd like to help test, have suggestions, etc.
|
||||
</p>
|
||||
|
||||
|
@ -254,6 +254,36 @@
|
|||
|
||||
</section>
|
||||
|
||||
<section name="Search Narrowind">
|
||||
|
||||
<p>
|
||||
HAPI FHIR 3.7.0 introduced a new interceptor, the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/auth/SearchNarrowingInterceptor.html">SearchNarrowingInterceptor</a>.
|
||||
</p>
|
||||
<p>
|
||||
This interceptor is designed to be used in conjunction with AuthorizationInterceptor. It
|
||||
uses a similar strategy where a dynamic list is built up for each request, but the
|
||||
purpose of this interceptor is to modify client searches that are received (after
|
||||
HAPI FHIR received the HTTP request, but before the search is actually performed)
|
||||
to restrict the search to only search for specific resources or compartments that the
|
||||
user has access to.
|
||||
</p>
|
||||
<p>
|
||||
This could be used, for example, to allow the user to perform a search for<br/>
|
||||
<code>http://baseurl/Observation?category=laboratory</code><br/>
|
||||
and then receive results as though they had requested<br/>
|
||||
<code>http://baseurl/Observation?subject=Patient/123&category=laboratory</code>.
|
||||
</p>
|
||||
<p>
|
||||
An example of this interceptor follows:
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="narrowing" />
|
||||
<param name="file" value="examples/src/main/java/example/AuthorizationInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
||||
</document>
|
||||
|
|
Loading…
Reference in New Issue