mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-02-18 10:55:22 +00:00
Merge branch 'master' into subscription-bugfix
This commit is contained in:
commit
068117138e
@ -1,15 +1,11 @@
|
|||||||
package example;
|
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.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
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.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
@ -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.exceptions.AuthenticationException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.*;
|
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")
|
@SuppressWarnings("unused")
|
||||||
public class AuthorizationInterceptors {
|
public class AuthorizationInterceptors {
|
||||||
@ -158,4 +160,47 @@ public class AuthorizationInterceptors {
|
|||||||
//END SNIPPET: patchAll
|
//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;
|
package ca.uhn.fhir.rest.gclient;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
@ -98,6 +100,18 @@ public class ReferenceClientParam extends BaseClientParam implements IParam {
|
|||||||
return new StringCriterion<>(getParamName(), theIds);
|
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 static class ReferenceChainCriterion implements ICriterion<ReferenceClientParam>, ICriterionInternal {
|
||||||
|
|
||||||
private final String myResourceTypeQualifier;
|
private final String myResourceTypeQualifier;
|
||||||
|
@ -62,5 +62,10 @@ public abstract class BaseAndListParam<T extends IQueryParameterOr<?>> implement
|
|||||||
return myValues.toString();
|
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.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
@ -208,6 +210,13 @@ public class ParameterUtil {
|
|||||||
|| IPrimitiveType.class.isAssignableFrom(theClass);
|
|| 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) {
|
public static int nonEscapedIndexOf(String theString, char theCharacter) {
|
||||||
for (int i = 0; i < theString.length(); i++) {
|
for (int i = 0; i < theString.length(); i++) {
|
||||||
if (theString.charAt(i) == theCharacter) {
|
if (theString.charAt(i) == theCharacter) {
|
||||||
|
@ -29,14 +29,14 @@ public interface IAnyResource extends IBaseResource {
|
|||||||
* Search parameter constant for <b>_language</b>
|
* Search parameter constant for <b>_language</b>
|
||||||
*/
|
*/
|
||||||
@SearchParamDefinition(name="_language", path="", description="The language of the resource", type="string" )
|
@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>
|
* Search parameter constant for <b>_id</b>
|
||||||
*/
|
*/
|
||||||
@SearchParamDefinition(name="_id", path="", description="The ID of the resource", type="token" )
|
@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>
|
* <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>
|
* Path: <b>Resource._id</b><br>
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public static final TokenClientParam RES_ID = new TokenClientParam(IAnyResource.SP_RES_ID);
|
TokenClientParam RES_ID = new TokenClientParam(IAnyResource.SP_RES_ID);
|
||||||
|
|
||||||
String getId();
|
String getId();
|
||||||
|
|
||||||
@ -55,11 +55,11 @@ public interface IAnyResource extends IBaseResource {
|
|||||||
|
|
||||||
IPrimitiveType<String> getLanguageElement();
|
IPrimitiveType<String> getLanguageElement();
|
||||||
|
|
||||||
public Object getUserData(String name);
|
Object getUserData(String name);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
IAnyResource setId(String theId);
|
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();
|
conformance = (IBaseResource) client.fetchConformance().ofType(implementingClass).execute();
|
||||||
} catch (FhirClientConnectionException e) {
|
} catch (FhirClientConnectionException e) {
|
||||||
if (!myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) && e.getCause() instanceof DataFormatException) {
|
if (!myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) && e.getCause() instanceof DataFormatException) {
|
||||||
capabilityStatementResourceName = "Conformance";
|
capabilityStatementResourceName = "CapabilityStatement";
|
||||||
implementingClass = myContext.getResourceDefinition(capabilityStatementResourceName).getImplementingClass();
|
implementingClass = myContext.getResourceDefinition(capabilityStatementResourceName).getImplementingClass();
|
||||||
conformance = (IBaseResource) client.fetchConformance().ofType(implementingClass).execute();
|
conformance = (IBaseResource) client.fetchConformance().ofType(implementingClass).execute();
|
||||||
} else {
|
} else {
|
||||||
|
@ -81,7 +81,7 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||||||
String paramName = isNotBlank(qualifier) ? getName() + qualifier : getName();
|
String paramName = isNotBlank(qualifier) ? getName() + qualifier : getName();
|
||||||
List<String> paramValues = theTargetQueryArguments.get(paramName);
|
List<String> paramValues = theTargetQueryArguments.get(paramName);
|
||||||
if (paramValues == null) {
|
if (paramValues == null) {
|
||||||
paramValues = new ArrayList<String>(value.size());
|
paramValues = new ArrayList<>(value.size());
|
||||||
theTargetQueryArguments.put(paramName, paramValues);
|
theTargetQueryArguments.put(paramName, paramValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +233,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle, MetaDt> {
|
|||||||
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
|
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
|
||||||
Entry nextRespEntry = response.getEntry().get(originalOrder);
|
Entry nextRespEntry = response.getEntry().get(originalOrder);
|
||||||
|
|
||||||
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
|
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails(theRequestDetails);
|
||||||
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
|
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
|
||||||
requestDetails.setRequestType(RequestTypeEnum.GET);
|
requestDetails.setRequestType(RequestTypeEnum.GET);
|
||||||
requestDetails.setServer(theRequestDetails.getServer());
|
requestDetails.setServer(theRequestDetails.getServer());
|
||||||
|
@ -384,7 +384,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
|
|||||||
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
|
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
|
||||||
BUNDLEENTRY nextRespEntry = myVersionAdapter.getEntries(response).get(originalOrder);
|
BUNDLEENTRY nextRespEntry = myVersionAdapter.getEntries(response).get(originalOrder);
|
||||||
|
|
||||||
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
|
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails(theRequestDetails);
|
||||||
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
|
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
|
||||||
requestDetails.setRequestType(RequestTypeEnum.GET);
|
requestDetails.setRequestType(RequestTypeEnum.GET);
|
||||||
requestDetails.setServer(theRequestDetails.getServer());
|
requestDetails.setServer(theRequestDetails.getServer());
|
||||||
|
@ -29,11 +29,25 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
|||||||
|
|
||||||
public class ServletSubRequestDetails extends 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) {
|
public void addHeader(String theName, String theValue) {
|
||||||
String lowerCase = theName.toLowerCase();
|
String lowerCase = theName.toLowerCase();
|
||||||
ArrayList<String> list = myHeaders.get(lowerCase);
|
List<String> list = myHeaders.get(lowerCase);
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
list = new ArrayList<>();
|
list = new ArrayList<>();
|
||||||
myHeaders.put(lowerCase, list);
|
myHeaders.put(lowerCase, list);
|
||||||
@ -43,7 +57,7 @@ public class ServletSubRequestDetails extends ServletRequestDetails {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHeader(String theName) {
|
public String getHeader(String theName) {
|
||||||
ArrayList<String> list = myHeaders.get(theName.toLowerCase());
|
List<String> list = myHeaders.get(theName.toLowerCase());
|
||||||
if (list == null || list.isEmpty()) {
|
if (list == null || list.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -52,7 +66,7 @@ public class ServletSubRequestDetails extends ServletRequestDetails {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getHeaders(String theName) {
|
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()) {
|
if (list == null || list.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,13 @@ public class SubscriptionInterceptorLoader {
|
|||||||
mySubscriptionMatcherInterceptor = myAppicationContext.getBean(SubscriptionMatcherInterceptor.class);
|
mySubscriptionMatcherInterceptor = myAppicationContext.getBean(SubscriptionMatcherInterceptor.class);
|
||||||
}
|
}
|
||||||
ourLog.info("Registering subscription matcher interceptor");
|
ourLog.info("Registering subscription matcher interceptor");
|
||||||
|
|
||||||
|
if (mySubscriptionMatcherInterceptor == null) {
|
||||||
|
mySubscriptionMatcherInterceptor = myAppicationContext.getBean(SubscriptionMatcherInterceptor.class);
|
||||||
|
}
|
||||||
|
|
||||||
myDaoConfig.registerInterceptor(mySubscriptionMatcherInterceptor);
|
myDaoConfig.registerInterceptor(mySubscriptionMatcherInterceptor);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +42,7 @@ public class DaoSubscriptionProvider implements ISubscriptionProvider {
|
|||||||
@Override
|
@Override
|
||||||
public IBundleProvider search(SearchParameterMap theMap) {
|
public IBundleProvider search(SearchParameterMap theMap) {
|
||||||
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
||||||
RequestDetails req = new ServletSubRequestDetails();
|
return subscriptionDao.search(theMap);
|
||||||
req.setSubRequest(true);
|
|
||||||
|
|
||||||
return subscriptionDao.search(theMap, req);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,12 +72,9 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher {
|
|||||||
RuntimeResourceDefinition responseResourceDef = subscriptionDao.validateCriteriaAndReturnResourceDefinition(theCriteria);
|
RuntimeResourceDefinition responseResourceDef = subscriptionDao.validateCriteriaAndReturnResourceDefinition(theCriteria);
|
||||||
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef);
|
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef);
|
||||||
|
|
||||||
RequestDetails req = new ServletSubRequestDetails();
|
|
||||||
req.setSubRequest(true);
|
|
||||||
|
|
||||||
IFhirResourceDao<? extends IBaseResource> responseDao = myDaoRegistry.getResourceDao(responseResourceDef.getImplementingClass());
|
IFhirResourceDao<? extends IBaseResource> responseDao = myDaoRegistry.getResourceDao(responseResourceDef.getImplementingClass());
|
||||||
responseCriteriaUrl.setLoadSynchronousUpTo(1);
|
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.Constants;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
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.ForbiddenOperationException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
@ -28,6 +30,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.in;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
import static org.junit.Assert.*;
|
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
|
* See #751
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ca.uhn.fhirtest.config;
|
package ca.uhn.fhirtest.config;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
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.config.BaseJavaConfigDstu2;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
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.search.LuceneSearchMappingFactory;
|
||||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
|
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
|
||||||
@ -72,6 +73,11 @@ public class TdlDstu2Config extends BaseJavaConfigDstu2 {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ModelConfig modelConfig() {
|
||||||
|
return daoConfig().getModelConfig();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(name = "myPersistenceDataSourceDstu1", destroyMethod = "close")
|
@Bean(name = "myPersistenceDataSourceDstu1", destroyMethod = "close")
|
||||||
public DataSource dataSource() {
|
public DataSource dataSource() {
|
||||||
BasicDataSource retVal = new BasicDataSource();
|
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.config.BaseJavaConfigDstu3;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
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.search.LuceneSearchMappingFactory;
|
||||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||||
@ -59,6 +60,11 @@ public class TdlDstu3Config extends BaseJavaConfigDstu3 {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ModelConfig modelConfig() {
|
||||||
|
return daoConfig().getModelConfig();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(name = "myPersistenceDataSourceDstu3", destroyMethod = "close")
|
@Bean(name = "myPersistenceDataSourceDstu3", destroyMethod = "close")
|
||||||
public DataSource dataSource() {
|
public DataSource dataSource() {
|
||||||
BasicDataSource retVal = new BasicDataSource();
|
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.config.BaseJavaConfigDstu2;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
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.search.LuceneSearchMappingFactory;
|
||||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
@ -71,6 +72,11 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ModelConfig modelConfig() {
|
||||||
|
return daoConfig().getModelConfig();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(name = "myPersistenceDataSourceDstu1", destroyMethod = "close")
|
@Bean(name = "myPersistenceDataSourceDstu1", destroyMethod = "close")
|
||||||
public DataSource dataSource() {
|
public DataSource dataSource() {
|
||||||
BasicDataSource retVal = new BasicDataSource();
|
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.config.BaseJavaConfigDstu3;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
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.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
||||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||||
@ -64,6 +65,12 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ModelConfig modelConfig() {
|
||||||
|
return daoConfig().getModelConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Bean(autowire = Autowire.BY_TYPE)
|
@Bean(autowire = Autowire.BY_TYPE)
|
||||||
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
|
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
|
||||||
|
@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config;
|
|||||||
|
|
||||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigR4;
|
import ca.uhn.fhir.jpa.config.BaseJavaConfigR4;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
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.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
||||||
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
|
||||||
@ -63,6 +64,12 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ModelConfig modelConfig() {
|
||||||
|
return daoConfig().getModelConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Bean(name = "myPersistenceDataSourceR4", destroyMethod = "close")
|
@Bean(name = "myPersistenceDataSourceR4", destroyMethod = "close")
|
||||||
public DataSource dataSource() {
|
public DataSource dataSource() {
|
||||||
BasicDataSource retVal = new BasicDataSource();
|
BasicDataSource retVal = new BasicDataSource();
|
||||||
|
@ -17,7 +17,7 @@ public class UhnFhirTestApp {
|
|||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
||||||
int myPort = 8888;
|
int myPort = 8889;
|
||||||
String base = "http://localhost:" + myPort + "/baseDstu2";
|
String base = "http://localhost:" + myPort + "/baseDstu2";
|
||||||
|
|
||||||
// new File("target/testdb").mkdirs();
|
// new File("target/testdb").mkdirs();
|
||||||
|
@ -59,6 +59,10 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-collections4</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</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>
|
* <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.
|
* for information on how to use this interceptor.
|
||||||
* </p>
|
* </p>
|
||||||
|
*
|
||||||
|
* @see SearchNarrowingInterceptor
|
||||||
*/
|
*/
|
||||||
public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter implements IRuleApplier {
|
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.InputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
@ -145,4 +143,18 @@ public class ServletRequestDetails extends RequestDetails {
|
|||||||
this.myServletResponse = myServletResponse;
|
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);
|
param.setName(nextParamUnchainedName);
|
||||||
if (StringUtils.isNotBlank(chain)) {
|
if (StringUtils.isNotBlank(chain)) {
|
||||||
param.addChain(chain);
|
param.addChain(chain);
|
||||||
}
|
} else {
|
||||||
|
if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
|
||||||
if (nextParameter.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
|
for (String nextWhitelist : new TreeSet<String>(nextParameter.getQualifierWhitelist())) {
|
||||||
for (String nextWhitelist : new TreeSet<String>(nextParameter.getQualifierWhitelist())) {
|
if (nextWhitelist.startsWith(".")) {
|
||||||
if (nextWhitelist.startsWith(".")) {
|
param.addChain(nextWhitelist.substring(1));
|
||||||
param.addChain(nextWhitelist.substring(1));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,6 +579,45 @@ public class ServerConformanceProviderDstu2Test {
|
|||||||
assertEquals(2, param.getChain().size());
|
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
|
@Test
|
||||||
public void testSystemHistorySupported() throws Exception {
|
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 {
|
public static class SystemHistoryProvider {
|
||||||
|
|
||||||
@History
|
@History
|
||||||
|
@ -1,56 +1,50 @@
|
|||||||
package org.hl7.fhir.dstu3.hapi.rest.server;
|
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.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
import ca.uhn.fhir.model.api.Include;
|
||||||
import ca.uhn.fhir.model.api.annotation.Description;
|
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.annotation.*;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import ca.uhn.fhir.rest.param.*;
|
||||||
import ca.uhn.fhir.rest.server.*;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
import ca.uhn.fhir.rest.server.method.*;
|
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.rest.server.method.SearchParameter;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
import ca.uhn.fhir.validation.FhirValidator;
|
import ca.uhn.fhir.validation.FhirValidator;
|
||||||
import ca.uhn.fhir.validation.ValidationResult;
|
import ca.uhn.fhir.validation.ValidationResult;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import com.google.common.collect.Lists;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import org.hl7.fhir.dstu3.model.*;
|
||||||
import static org.hamcrest.Matchers.is;
|
import org.hl7.fhir.dstu3.model.CapabilityStatement.*;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
|
import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
|
||||||
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationDefinitionParameterComponent;
|
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationDefinitionParameterComponent;
|
||||||
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationKind;
|
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationKind;
|
||||||
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationParameterUse;
|
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 {
|
public class ServerCapabilityStatementProviderDstu3Test {
|
||||||
|
|
||||||
private static FhirContext ourCtx;
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProviderDstu3Test.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProviderDstu3Test.class);
|
||||||
|
private static FhirContext ourCtx;
|
||||||
private static FhirValidator ourValidator;
|
private static FhirValidator ourValidator;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -88,6 +82,47 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
return resource;
|
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
|
@Test
|
||||||
public void testConditionalOperations() throws Exception {
|
public void testConditionalOperations() throws Exception {
|
||||||
|
|
||||||
@ -235,7 +270,9 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
assertNull(res.getConditionalUpdateElement().getValue());
|
assertNull(res.getConditionalUpdateElement().getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** See #379 */
|
/**
|
||||||
|
* See #379
|
||||||
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testOperationAcrossMultipleTypes() throws Exception {
|
public void testOperationAcrossMultipleTypes() throws Exception {
|
||||||
RestfulServer rs = new RestfulServer(ourCtx);
|
RestfulServer rs = new RestfulServer(ourCtx);
|
||||||
@ -544,7 +581,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
@Test
|
@Test
|
||||||
public void testSearchReferenceParameterWithList() throws Exception {
|
public void testSearchReferenceParameterWithList() throws Exception {
|
||||||
|
|
||||||
RestfulServer rsNoType = new RestfulServer(ourCtx){
|
RestfulServer rsNoType = new RestfulServer(ourCtx) {
|
||||||
@Override
|
@Override
|
||||||
public RestulfulServerConfiguration createConfiguration() {
|
public RestulfulServerConfiguration createConfiguration() {
|
||||||
RestulfulServerConfiguration retVal = super.createConfiguration();
|
RestulfulServerConfiguration retVal = super.createConfiguration();
|
||||||
@ -561,7 +598,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
String confNoType = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
|
String confNoType = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
|
||||||
ourLog.info(confNoType);
|
ourLog.info(confNoType);
|
||||||
|
|
||||||
RestfulServer rsWithType = new RestfulServer(ourCtx){
|
RestfulServer rsWithType = new RestfulServer(ourCtx) {
|
||||||
@Override
|
@Override
|
||||||
public RestulfulServerConfiguration createConfiguration() {
|
public RestulfulServerConfiguration createConfiguration() {
|
||||||
RestulfulServerConfiguration retVal = super.createConfiguration();
|
RestulfulServerConfiguration retVal = super.createConfiguration();
|
||||||
@ -720,8 +757,8 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
assertThat(param.getUse(), is(OperationParameterUse.IN));
|
assertThat(param.getUse(), is(OperationParameterUse.IN));
|
||||||
|
|
||||||
CapabilityStatementRestResourceComponent patientResource = restComponent.getResource().stream()
|
CapabilityStatementRestResourceComponent patientResource = restComponent.getResource().stream()
|
||||||
.filter(r -> patientResourceName.equals(r.getType()))
|
.filter(r -> patientResourceName.equals(r.getType()))
|
||||||
.findAny().get();
|
.findAny().get();
|
||||||
assertThat("Named query parameters should not appear in the resource search params", patientResource.getSearchParam(), is(empty()));
|
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());
|
assertTrue(outcome, result.isSuccessful());
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
public static class SearchProviderWithExplicitChains {
|
||||||
public static void afterClassClearContext() {
|
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
@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")
|
@SuppressWarnings("unused")
|
||||||
@ -836,7 +881,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
|
|
||||||
@Search(type = Patient.class)
|
@Search(type = Patient.class)
|
||||||
public Patient findPatient(@Description(shortDefinition = "The patient's identifier") @OptionalParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier,
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -847,7 +892,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
|
|
||||||
@Operation(name = "someOp")
|
@Operation(name = "someOp")
|
||||||
public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam IdType theId,
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -868,7 +913,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
|
|
||||||
@Operation(name = "someOp")
|
@Operation(name = "someOp")
|
||||||
public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam IdType theId,
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -912,9 +957,9 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static class PlainProviderWithExtendedOperationOnNoType {
|
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,
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -925,7 +970,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
|
|
||||||
@Operation(name = "everything", idempotent = true)
|
@Operation(name = "everything", idempotent = true)
|
||||||
public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam IdType theId, @OperationParam(name = "start") DateType theStart,
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -942,8 +987,8 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
@Description(shortDefinition = "This is a search for stuff!")
|
@Description(shortDefinition = "This is a search for stuff!")
|
||||||
@Search
|
@Search
|
||||||
public List<DiagnosticReport> findDiagnosticReportsByPatient(@RequiredParam(name = DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) TokenParam thePatientId,
|
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,
|
@OptionalParam(name = DiagnosticReport.SP_CODE) TokenOrListParam theNames, @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange,
|
||||||
@IncludeParam(allow = { "DiagnosticReport.result" }) Set<Include> theIncludes) throws Exception {
|
@IncludeParam(allow = {"DiagnosticReport.result"}) Set<Include> theIncludes) throws Exception {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -974,7 +1019,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
|
|
||||||
@Search(type = Patient.class)
|
@Search(type = Patient.class)
|
||||||
public Patient findPatient2(
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -984,15 +1029,15 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
public static class SearchProviderWithWhitelist {
|
public static class SearchProviderWithWhitelist {
|
||||||
|
|
||||||
@Search(type = Patient.class)
|
@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",
|
public Patient findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION, chainWhitelist = {"foo",
|
||||||
"bar" }) ReferenceAndListParam theIdentifier) {
|
"bar"}) ReferenceAndListParam theIdentifier) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static class SearchProviderWithListNoType implements IResourceProvider {
|
public static class SearchProviderWithListNoType implements IResourceProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends IBaseResource> getResourceType() {
|
public Class<? extends IBaseResource> getResourceType() {
|
||||||
@ -1000,7 +1045,6 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Search()
|
@Search()
|
||||||
public List<Patient> findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION) ReferenceAndListParam theIdentifier) {
|
public List<Patient> findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION) ReferenceAndListParam theIdentifier) {
|
||||||
return null;
|
return null;
|
||||||
@ -1009,7 +1053,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static class SearchProviderWithListWithType implements IResourceProvider {
|
public static class SearchProviderWithListWithType implements IResourceProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends IBaseResource> getResourceType() {
|
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) {
|
public List<Patient> findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION) ReferenceAndListParam theIdentifier) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class SystemHistoryProvider {
|
public static class SystemHistoryProvider {
|
||||||
|
|
||||||
@History
|
@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;
|
package ca.uhn.fhir.rest.client;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import ca.uhn.fhir.model.api.Include;
|
||||||
import static org.junit.Assert.assertThat;
|
import ca.uhn.fhir.rest.annotation.IncludeParam;
|
||||||
import static org.junit.Assert.assertTrue;
|
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||||
import static org.mockito.Mockito.mock;
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
import static org.mockito.Mockito.when;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||||
import java.io.StringReader;
|
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||||
import java.nio.charset.Charset;
|
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||||
import java.util.*;
|
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.IOUtils;
|
||||||
import org.apache.commons.io.input.ReaderInputStream;
|
import org.apache.commons.io.input.ReaderInputStream;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
@ -23,118 +26,144 @@ import org.apache.http.message.BasicStatusLine;
|
|||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
import org.hl7.fhir.r4.model.Encounter;
|
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.ArgumentCaptor;
|
||||||
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import java.io.StringReader;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
import java.nio.charset.Charset;
|
||||||
import ca.uhn.fhir.rest.annotation.*;
|
import java.util.HashSet;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import java.util.List;
|
||||||
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
import java.util.Set;
|
||||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
import static org.junit.Assert.*;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import static org.mockito.Mockito.mock;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import static org.mockito.Mockito.when;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
|
||||||
|
|
||||||
public class SearchClientTest {
|
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 FhirContext ourCtx;
|
||||||
private HttpClient ourHttpClient;
|
private HttpClient ourHttpClient;
|
||||||
private HttpResponse ourHttpResponse;
|
private HttpResponse ourHttpResponse;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
ourCtx = FhirContext.forR4();
|
ourCtx = FhirContext.forR4();
|
||||||
|
|
||||||
ourHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
|
ourHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
|
||||||
ourCtx.getRestfulClientFactory().setHttpClient(ourHttpClient);
|
ourCtx.getRestfulClientFactory().setHttpClient(ourHttpClient);
|
||||||
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
|
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
|
||||||
|
|
||||||
ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
|
ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPostOnLongParamsList() throws Exception {
|
public void testPostOnLongParamsList() throws Exception {
|
||||||
String resp = createBundle();
|
String resp = createBundle();
|
||||||
|
|
||||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
||||||
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
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().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")));
|
when(ourHttpResponse.getEntity().getContent()).thenAnswer(t->new ReaderInputStream(new StringReader(resp), Charset.forName("UTF-8")));
|
||||||
|
|
||||||
ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo");
|
ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo");
|
||||||
Set<Include> includes = new HashSet<Include>();
|
Set<Include> includes = new HashSet<Include>();
|
||||||
includes.add(new Include("one"));
|
includes.add(new Include("one"));
|
||||||
includes.add(new Include("two"));
|
includes.add(new Include("two"));
|
||||||
TokenOrListParam params = new TokenOrListParam();
|
TokenOrListParam params = new TokenOrListParam();
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (int i = 0; i < 1000; i++) {
|
||||||
params.add(new TokenParam("system", "value"));
|
params.add(new TokenParam("system", "value"));
|
||||||
}
|
}
|
||||||
List<Encounter> found = client.searchByList(params, includes);
|
|
||||||
|
|
||||||
assertEquals(1, found.size());
|
// With OR list
|
||||||
|
|
||||||
Encounter encounter = found.get(0);
|
List<Encounter> found = client.searchByList(params, includes);
|
||||||
assertNotNull(encounter.getSubject().getReference());
|
|
||||||
HttpUriRequest value = capt.getValue();
|
|
||||||
|
|
||||||
assertTrue("Expected request of type POST on long params list", value instanceof HttpPost);
|
assertEquals(1, found.size());
|
||||||
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
Encounter encounter = found.get(0);
|
||||||
public void testReturnTypedList() throws Exception {
|
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);
|
// With AND list
|
||||||
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")));
|
|
||||||
|
|
||||||
ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo");
|
TokenAndListParam paramsAndList = new TokenAndListParam();
|
||||||
List<Encounter> found = client.search();
|
paramsAndList.addAnd(params);
|
||||||
assertEquals(1, found.size());
|
found = client.searchByList(paramsAndList, includes);
|
||||||
|
|
||||||
Encounter encounter = found.get(0);
|
assertEquals(1, found.size());
|
||||||
assertNotNull(encounter.getSubject().getReference());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String createBundle() {
|
encounter = found.get(0);
|
||||||
Bundle bundle = new Bundle();
|
assertNotNull(encounter.getSubject().getReference());
|
||||||
|
value = capt.getAllValues().get(1);
|
||||||
|
|
||||||
Encounter enc = new Encounter();
|
assertTrue("Expected request of type POST on long params list", value instanceof HttpPost);
|
||||||
enc.getSubject().setReference("Patient/1");
|
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);
|
String resp = createBundle();
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
ITestClient client = ourCtx.newRestfulClient(ITestClient.class, "http://foo");
|
||||||
List<Encounter> search();
|
List<Encounter> found = client.search();
|
||||||
|
assertEquals(1, found.size());
|
||||||
|
|
||||||
@Search
|
Encounter encounter = found.get(0);
|
||||||
List<Encounter> searchByList(@RequiredParam(name = Encounter.SP_IDENTIFIER) TokenOrListParam tokenOrListParam, @IncludeParam Set<Include> theIncludes) throws BaseServerResponseException;
|
assertNotNull(encounter.getSubject().getReference());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
private String createBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
|
||||||
@AfterClass
|
Encounter enc = new Encounter();
|
||||||
public static void afterClassClearContext() {
|
enc.getSubject().setReference("Patient/1");
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
|
||||||
}
|
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>
|
<commons_lang3_version>3.8.1</commons_lang3_version>
|
||||||
<derby_version>10.14.2.0</derby_version>
|
<derby_version>10.14.2.0</derby_version>
|
||||||
<error_prone_annotations_version>2.0.18</error_prone_annotations_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>
|
<guava_version>25.0-jre</guava_version>
|
||||||
<gson_version>2.8.5</gson_version>
|
<gson_version>2.8.5</gson_version>
|
||||||
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
|
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
|
||||||
@ -639,7 +640,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.errorprone</groupId>
|
<groupId>com.google.errorprone</groupId>
|
||||||
<artifactId>error_prone_core</artifactId>
|
<artifactId>error_prone_core</artifactId>
|
||||||
<version>2.3.2</version>
|
<version>${error_prone_core_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
@ -766,7 +767,8 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.microsoft.sqlserver</groupId>
|
<groupId>com.microsoft.sqlserver</groupId>
|
||||||
<artifactId>mssql-jdbc</artifactId>
|
<artifactId>mssql-jdbc</artifactId>
|
||||||
<version>6.2.2.jre8</version>
|
<!--<version>6.2.2.jre8</version>-->
|
||||||
|
<version>7.0.0.jre8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--
|
<!--
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -2308,7 +2310,7 @@
|
|||||||
<path>
|
<path>
|
||||||
<groupId>com.google.errorprone</groupId>
|
<groupId>com.google.errorprone</groupId>
|
||||||
<artifactId>error_prone_core</artifactId>
|
<artifactId>error_prone_core</artifactId>
|
||||||
<version>2.3.2</version>
|
<version>${error_prone_core_version}</version>
|
||||||
</path>
|
</path>
|
||||||
</annotationProcessorPaths>
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -264,6 +264,31 @@
|
|||||||
OperationDefinitions are now created for named queries in server
|
OperationDefinitions are now created for named queries in server
|
||||||
module. Thanks to Stig Døssing for the pull request!
|
module. Thanks to Stig Døssing for the pull request!
|
||||||
</action>
|
</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>
|
||||||
<release version="3.6.0" date="2018-11-12" description="Food">
|
<release version="3.6.0" date="2018-11-12" description="Food">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
@ -96,10 +96,10 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="doc_info_bubble">
|
<p class="doc_info_bubble">
|
||||||
AuthorizationInterceptor is a new feature in HAPI FHIR, and has not yet
|
AuthorizationInterceptor has been well tested, but it is impossible to
|
||||||
been heavily tested. Use with caution, and do lots of testing! We welcome
|
predeict every scenario and environment in which HAPI FHIR will be used.
|
||||||
feedback and suggestions on this feature. In addition, this documentation is
|
Use with caution, and do lots of testing! We welcome
|
||||||
not yet complete. More examples and details will be added soon! Please get in
|
feedback and suggestions on this feature. Please get in
|
||||||
touch if you'd like to help test, have suggestions, etc.
|
touch if you'd like to help test, have suggestions, etc.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -254,6 +254,36 @@
|
|||||||
|
|
||||||
</section>
|
</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>
|
</body>
|
||||||
|
|
||||||
</document>
|
</document>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user