Merge remote-tracking branch 'origin/master' into documentOperation

This commit is contained in:
patrick-werner 2018-06-19 16:57:54 +02:00
commit b76ad6870f
38 changed files with 2819 additions and 2719 deletions

View File

@ -185,14 +185,29 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
});
mySearchParams = Collections.unmodifiableList(searchParams);
Map<String, List<RuntimeSearchParam>> compartmentNameToSearchParams = new HashMap<String, List<RuntimeSearchParam>>();
Map<String, List<RuntimeSearchParam>> compartmentNameToSearchParams = new HashMap<>();
for (RuntimeSearchParam next : searchParams) {
if (next.getProvidesMembershipInCompartments() != null) {
for (String nextCompartment : next.getProvidesMembershipInCompartments()) {
if (!compartmentNameToSearchParams.containsKey(nextCompartment)) {
compartmentNameToSearchParams.put(nextCompartment, new ArrayList<RuntimeSearchParam>());
compartmentNameToSearchParams.put(nextCompartment, new ArrayList<>());
}
List<RuntimeSearchParam> searchParamsForCompartment = compartmentNameToSearchParams.get(nextCompartment);
searchParamsForCompartment.add(next);
/*
* If one search parameter marks an SP as making a resource
* a part of a compartment, let's also denote all other
* SPs with the same path the same way. This behaviour is
* used by AuthorizationInterceptor
*/
for (RuntimeSearchParam nextAlternate : searchParams) {
if (nextAlternate.getPath().equals(next.getPath())) {
if (!nextAlternate.getName().equals(next.getName())) {
searchParamsForCompartment.add(nextAlternate);
}
}
}
compartmentNameToSearchParams.get(nextCompartment).add(next);
}
}
}

View File

@ -5,6 +5,10 @@ import static org.apache.commons.lang3.StringUtils.trim;
import java.util.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
@ -38,6 +42,18 @@ public class RuntimeSearchParam {
private final RestSearchParameterTypeEnum myParamType;
private final String myPath;
private final Set<String> myTargets;
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("base", myBase)
.append("name", myName)
.append("path", myPath)
.append("id", myId)
.append("uri", myUri)
.toString();
}
private final Set<String> myProvidesMembershipInCompartments;
private final RuntimeSearchParamStatusEnum myStatus;
private final String myUri;
@ -55,9 +71,36 @@ public class RuntimeSearchParam {
this(theId, theUri, theName, theDescription, thePath, theParamType, theCompositeOf, theProvidesMembershipInCompartments, theTargets, theStatus, null);
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (theO == null || getClass() != theO.getClass()) return false;
RuntimeSearchParam that = (RuntimeSearchParam) theO;
return new EqualsBuilder()
.append(getId(), that.getId())
.append(getName(), that.getName())
.append(getPath(), that.getPath())
.append(getUri(), that.getUri())
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(getId())
.append(getName())
.append(getPath())
.append(getUri())
.toHashCode();
}
public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List<RuntimeSearchParam> theCompositeOf,
Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, Collection<String> theBase) {
Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, Collection<String> theBase) {
super();
myId = theId;
myUri = theUri;
myName = theName;

View File

@ -23,23 +23,23 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.QuoteMode;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.convertors.VersionConvertor_30_40;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.apache.commons.lang3.StringUtils.defaultString;
@ -72,11 +72,11 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept
}
@Override
protected void process() throws ParseException {
protected void process() throws ExecutionException {
searchForConceptMapByUrl();
}
private void searchForConceptMapByUrl() {
private void searchForConceptMapByUrl() throws ExecutionException {
ourLog.info("Searching for ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
if (fhirVersion == FhirVersionEnum.DSTU3) {
org.hl7.fhir.dstu3.model.Bundle response = client
@ -111,61 +111,26 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept
}
}
private void convertConceptMapToCsv(org.hl7.fhir.dstu3.model.ConceptMap theConceptMap) {
ourLog.info("Exporting ConceptMap to CSV...");
BufferedWriter bufferedWriter = null;
CSVPrinter csvPrinter = null;
private void convertConceptMapToCsv(org.hl7.fhir.dstu3.model.ConceptMap theConceptMap) throws ExecutionException {
try {
bufferedWriter = Files.newBufferedWriter(Paths.get(file));
csvPrinter = new CSVPrinter(
bufferedWriter,
CSVFormat
.DEFAULT
.withRecordSeparator("\n")
.withHeader(Header.class));
for (org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent group : theConceptMap.getGroup()) {
for (org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent element : group.getElement()) {
for (org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent target : element.getTarget()) {
List<String> columns = new ArrayList<>();
columns.add(defaultString(group.getSource()));
columns.add(defaultString(group.getSourceVersion()));
columns.add(defaultString(group.getTarget()));
columns.add(defaultString(group.getTargetVersion()));
columns.add(defaultString(element.getCode()));
columns.add(defaultString(element.getDisplay()));
columns.add(defaultString(target.getCode()));
columns.add(defaultString(target.getDisplay()));
columns.add(defaultString(target.getEquivalence().toCode()));
columns.add(defaultString(target.getComment()));
csvPrinter.print(columns);
}
}
}
} catch (IOException ioe) {
throw new InternalErrorException(ioe);
} finally {
IOUtils.closeQuietly(csvPrinter);
IOUtils.closeQuietly(bufferedWriter);
convertConceptMapToCsv(VersionConvertor_30_40.convertConceptMap(theConceptMap));
} catch (FHIRException fe) {
throw new ExecutionException(fe);
}
ourLog.info("Finished exporting to {}", file);
}
private void convertConceptMapToCsv(ConceptMap theConceptMap) {
ourLog.info("Exporting ConceptMap to CSV...");
Writer writer = null;
CSVPrinter csvPrinter = null;
try {
writer = Files.newBufferedWriter(Paths.get(file));
csvPrinter = new CSVPrinter(
try (
Writer writer = Files.newBufferedWriter(Paths.get(file));
CSVPrinter csvPrinter = new CSVPrinter(
writer,
CSVFormat
.DEFAULT
.withRecordSeparator("\n")
.withHeader(Header.class).withQuoteMode(QuoteMode.ALL));
.withHeader(Header.class)
.withQuoteMode(QuoteMode.ALL));
) {
for (ConceptMapGroupComponent group : theConceptMap.getGroup()) {
for (SourceElementComponent element : group.getElement()) {
for (ConceptMap.TargetElementComponent target : element.getTarget()) {
@ -188,10 +153,8 @@ public class ExportConceptMapToCsvCommand extends AbstractImportExportCsvConcept
}
} catch (IOException ioe) {
throw new InternalErrorException(ioe);
} finally {
IOUtils.closeQuietly(csvPrinter);
IOUtils.closeQuietly(writer);
}
ourLog.info("Finished exporting to {}", file);
}
}

View File

@ -25,11 +25,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.convertors.VersionConvertor_30_40;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.ConceptMap;
@ -43,7 +41,10 @@ import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import static org.apache.commons.lang3.StringUtils.*;
@ -96,7 +97,7 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
}
@Override
protected void parseAdditionalParameters(CommandLine theCommandLine) throws ParseException {
protected void parseAdditionalParameters(CommandLine theCommandLine) {
sourceValueSet = theCommandLine.getOptionValue(SOURCE_VALUE_SET_PARAM);
if (isBlank(sourceValueSet)) {
ourLog.info("Source value set is not specified (i.e. ConceptMap.sourceUri).");
@ -113,11 +114,11 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
}
@Override
protected void process() throws ParseException, ExecutionException {
protected void process() throws ExecutionException {
searchForConceptMapByUrl();
}
private void searchForConceptMapByUrl() throws ParseException, ExecutionException {
private void searchForConceptMapByUrl() throws ExecutionException {
if (fhirVersion == FhirVersionEnum.DSTU3) {
org.hl7.fhir.dstu3.model.ConceptMap conceptMap = convertCsvToConceptMapDstu3();
@ -153,7 +154,7 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
}
}
private org.hl7.fhir.dstu3.model.ConceptMap convertCsvToConceptMapDstu3() throws ParseException, ExecutionException {
private org.hl7.fhir.dstu3.model.ConceptMap convertCsvToConceptMapDstu3() throws ExecutionException {
try {
return VersionConvertor_30_40.convertConceptMap(convertCsvToConceptMapR4());
} catch (FHIRException fe) {
@ -161,14 +162,12 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
}
}
private ConceptMap convertCsvToConceptMapR4() throws ParseException, ExecutionException {
private ConceptMap convertCsvToConceptMapR4() throws ExecutionException {
ourLog.info("Converting CSV to ConceptMap...");
ConceptMap retVal = new ConceptMap();
Reader reader = null;
CSVParser csvParser = null;
try {
reader = Files.newBufferedReader(Paths.get(file));
csvParser = new CSVParser(
try (
Reader reader = Files.newBufferedReader(Paths.get(file));
CSVParser csvParser = new CSVParser(
reader,
CSVFormat
.DEFAULT
@ -178,7 +177,7 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
.withIgnoreHeaderCase()
.withIgnoreEmptyLines()
.withTrim());
) {
retVal.setUrl(conceptMapUrl);
if (isNotBlank(sourceValueSet)) {
@ -278,9 +277,6 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept
}
} catch (IOException e) {
throw new InternalErrorException(e);
} finally {
IOUtils.closeQuietly(csvParser);
IOUtils.closeQuietly(reader);
}
ourLog.info("Finished converting CSV to ConceptMap.");

View File

@ -0,0 +1,283 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.ConceptMap;
import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.dstu3.model.UriType;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class ExportConceptMapToCsvCommandDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandDstu3Test.class);
private static final String CM_URL = "http://example.com/conceptmap";
private static final String VS_URL_1 = "http://example.com/valueset/1";
private static final String VS_URL_2 = "http://example.com/valueset/2";
private static final String CS_URL_1 = "http://example.com/codesystem/1";
private static final String CS_URL_2 = "http://example.com/codesystem/2";
private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILE = "./target/output.csv";
private static String ourBase;
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu3();
private static int ourPort;
private static Server ourServer;
private static String ourVersion = "dstu3";
static {
System.setProperty("test", "true");
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler servletHandler = new ServletHandler();
RestfulServer restfulServer = new RestfulServer(ourCtx);
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapDstu3(ourCtx));
ServletHolder servletHolder = new ServletHolder(restfulServer);
servletHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(servletHandler);
ourServer.start();
ourBase = "http://localhost:" + ourPort;
ourClient = ourCtx.newRestfulGenericClient(ourBase);
ourClient.create().resource(createConceptMap()).execute();
}
@Test
public void testExportConceptMapToCsvCommand() throws IOException {
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
App.main(new String[] {"export-conceptmap-to-csv",
"-v", ourVersion,
"-t", ourBase,
"-u", CM_URL,
"-f", FILE,
"-l"});
String expected = "\"SOURCE_CODE_SYSTEM\",\"SOURCE_CODE_SYSTEM_VERSION\",\"TARGET_CODE_SYSTEM\",\"TARGET_CODE_SYSTEM_VERSION\",\"SOURCE_CODE\",\"SOURCE_DISPLAY\",\"TARGET_CODE\",\"TARGET_DISPLAY\",\"EQUIVALENCE\",\"COMMENT\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1a\",\"Display 1a\",\"Code 2a\",\"Display 2a\",\"equal\",\"2a This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1b\",\"Display 1b\",\"Code 2b\",\"Display 2b\",\"equal\",\"2b This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1c\",\"Display 1c\",\"Code 2c\",\"Display 2c\",\"equal\",\"2c This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/2\",\"Version 2t\",\"Code 1d\",\"Display 1d\",\"Code 2d\",\"Display 2d\",\"equal\",\"2d This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1a\",\"Display 1a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1b\",\"Display 1b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1c\",\"Display 1c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
"\"http://example.com/codesystem/1\",\"Version 1s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 1d\",\"Display 1d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2a\",\"Display 2a\",\"Code 3a\",\"Display 3a\",\"equal\",\"3a This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2b\",\"Display 2b\",\"Code 3b\",\"Display 3b\",\"equal\",\"3b This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2c\",\"Display 2c\",\"Code 3c\",\"Display 3c\",\"equal\",\"3c This is a comment.\"\n" +
"\"http://example.com/codesystem/2\",\"Version 2s\",\"http://example.com/codesystem/3\",\"Version 3t\",\"Code 2d\",\"Display 2d\",\"Code 3d\",\"Display 3d\",\"equal\",\"3d This is a comment.\"\n";
String result = IOUtils.toString(new FileInputStream(FILE), Charsets.UTF_8);
assertEquals(expected, result);
FileUtils.deleteQuietly(new File(FILE));
}
static ConceptMap createConceptMap() {
ConceptMap conceptMap = new ConceptMap();
conceptMap
.setUrl(CM_URL)
.setSource(new UriType(VS_URL_1))
.setTarget(new UriType(VS_URL_2));
ConceptMap.ConceptMapGroupComponent group = conceptMap.addGroup();
group
.setSource(CS_URL_1)
.setSourceVersion("Version 1s")
.setTarget(CS_URL_2)
.setTargetVersion("Version 2t");
ConceptMap.SourceElementComponent element = group.addElement();
element
.setCode("Code 1a")
.setDisplay("Display 1a");
ConceptMap.TargetElementComponent target = element.addTarget();
target
.setCode("Code 2a")
.setDisplay("Display 2a")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("2a This is a comment.");
element = group.addElement();
element
.setCode("Code 1b")
.setDisplay("Display 1b");
target = element.addTarget();
target
.setCode("Code 2b")
.setDisplay("Display 2b")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("2b This is a comment.");
element = group.addElement();
element
.setCode("Code 1c")
.setDisplay("Display 1c");
target = element.addTarget();
target
.setCode("Code 2c")
.setDisplay("Display 2c")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("2c This is a comment.");
element = group.addElement();
element
.setCode("Code 1d")
.setDisplay("Display 1d");
target = element.addTarget();
target
.setCode("Code 2d")
.setDisplay("Display 2d")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("2d This is a comment.");
group = conceptMap.addGroup();
group
.setSource(CS_URL_1)
.setSourceVersion("Version 1s")
.setTarget(CS_URL_3)
.setTargetVersion("Version 3t");
element = group.addElement();
element
.setCode("Code 1a")
.setDisplay("Display 1a");
target = element.addTarget();
target
.setCode("Code 3a")
.setDisplay("Display 3a")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3a This is a comment.");
element = group.addElement();
element
.setCode("Code 1b")
.setDisplay("Display 1b");
target = element.addTarget();
target
.setCode("Code 3b")
.setDisplay("Display 3b")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3b This is a comment.");
element = group.addElement();
element
.setCode("Code 1c")
.setDisplay("Display 1c");
target = element.addTarget();
target
.setCode("Code 3c")
.setDisplay("Display 3c")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3c This is a comment.");
element = group.addElement();
element
.setCode("Code 1d")
.setDisplay("Display 1d");
target = element.addTarget();
target
.setCode("Code 3d")
.setDisplay("Display 3d")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3d This is a comment.");
group = conceptMap.addGroup();
group
.setSource(CS_URL_2)
.setSourceVersion("Version 2s")
.setTarget(CS_URL_3)
.setTargetVersion("Version 3t");
element = group.addElement();
element
.setCode("Code 2a")
.setDisplay("Display 2a");
target = element.addTarget();
target
.setCode("Code 3a")
.setDisplay("Display 3a")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3a This is a comment.");
element = group.addElement();
element
.setCode("Code 2b")
.setDisplay("Display 2b");
target = element.addTarget();
target
.setCode("Code 3b")
.setDisplay("Display 3b")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3b This is a comment.");
element = group.addElement();
element
.setCode("Code 2c")
.setDisplay("Display 2c");
target = element.addTarget();
target
.setCode("Code 3c")
.setDisplay("Display 3c")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3c This is a comment.");
element = group.addElement();
element
.setCode("Code 2d")
.setDisplay("Display 2d");
target = element.addTarget();
target
.setCode("Code 3d")
.setDisplay("Display 3d")
.setEquivalence(ConceptMapEquivalence.EQUAL)
.setComment("3d This is a comment.");
return conceptMap;
}
}

View File

@ -25,8 +25,8 @@ import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class ExportConceptMapToCsvCommandTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandTest.class);
public class ExportConceptMapToCsvCommandR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExportConceptMapToCsvCommandR4Test.class);
private static final String CM_URL = "http://example.com/conceptmap";
private static final String VS_URL_1 = "http://example.com/valueset/1";
private static final String VS_URL_2 = "http://example.com/valueset/2";
@ -40,6 +40,7 @@ public class ExportConceptMapToCsvCommandTest {
private static FhirContext ourCtx = FhirContext.forR4();
private static int ourPort;
private static Server ourServer;
private static String ourVersion = "r4";
static {
System.setProperty("test", "true");
@ -80,7 +81,7 @@ public class ExportConceptMapToCsvCommandTest {
ourLog.info("ConceptMap:\n" + ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createConceptMap()));
App.main(new String[] {"export-conceptmap-to-csv",
"-v", "r4",
"-v", ourVersion,
"-t", ourBase,
"-u", CM_URL,
"-f", FILE,

View File

@ -0,0 +1,127 @@
package ca.uhn.fhir.cli;
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.provider.AbstractHashMapResourceProvider;
import com.google.common.base.Charsets;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.dstu3.model.ConceptMap;
import org.hl7.fhir.dstu3.model.IdType;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* This is a subclass to implement FHIR operations specific to DSTU3 ConceptMap
* resources. Its superclass, {@link AbstractHashMapResourceProvider}, is a simple
* implementation of the resource provider interface that uses a HashMap to
* store all resources in memory.
* <p>
* This subclass currently supports the following FHIR operations:
* </p>
* <ul>
* <li>Search for DSTU3 ConceptMap resources by ConceptMap.url</li>
* <li>Conditional update for DSTU3 ConceptMap resources by ConceptMap.url</li>
* </ul>
*/
public class HashMapResourceProviderConceptMapDstu3 extends AbstractHashMapResourceProvider<ConceptMap> {
@SuppressWarnings("unchecked")
public HashMapResourceProviderConceptMapDstu3(FhirContext theFhirContext) {
super(theFhirContext, ConceptMap.class);
FhirVersionEnum fhirVersion = theFhirContext.getVersion().getVersion();
if (fhirVersion != FhirVersionEnum.DSTU3) {
throw new IllegalStateException("Requires FHIR version DSTU3. Unsupported FHIR version provided: " + fhirVersion);
}
}
@Search
public List<ConceptMap> searchByUrl(
@RequiredParam(name=ConceptMap.SP_URL) String theConceptMapUrl) {
List<ConceptMap> retVal = new ArrayList<>();
for (TreeMap<Long, ConceptMap> next : myIdToVersionToResourceMap.values()) {
if (!next.isEmpty()) {
ConceptMap conceptMap = next.lastEntry().getValue();
if (theConceptMapUrl.equals(conceptMap.getUrl()))
retVal.add(conceptMap);
break;
}
}
return retVal;
}
@Update
public MethodOutcome updateConceptMapConditional(
@ResourceParam ConceptMap theConceptMap,
@IdParam IdType theId,
@ConditionalUrlParam String theConditional) {
MethodOutcome methodOutcome = new MethodOutcome();
if (theConditional != null) {
String url = null;
try {
List<NameValuePair> params = URLEncodedUtils.parse(new URI(theConditional), Charsets.UTF_8);
for (NameValuePair param : params) {
if (param.getName().equalsIgnoreCase("url")) {
url = param.getValue();
break;
}
}
} catch (URISyntaxException urise) {
throw new InvalidRequestException(urise);
}
if (isNotBlank(url)) {
List<ConceptMap> conceptMaps = searchByUrl(url);
if (!conceptMaps.isEmpty()) {
methodOutcome = update(conceptMaps.get(0));
} else {
methodOutcome = create(theConceptMap);
}
}
} else {
methodOutcome = update(theConceptMap);
}
return methodOutcome;
}
}

View File

@ -0,0 +1,367 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.VerboseLoggingInterceptor;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.ConceptMap;
import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.*;
public class ImportCsvToConceptMapCommandDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommandDstu3Test.class);
private static final String CM_URL = "http://example.com/conceptmap";
private static final String VS_URL_1 = "http://example.com/valueset/1";
private static final String VS_URL_2 = "http://example.com/valueset/2";
private static final String CS_URL_1 = "http://example.com/codesystem/1";
private static final String CS_URL_2 = "http://example.com/codesystem/2";
private static final String CS_URL_3 = "http://example.com/codesystem/3";
private static final String FILENAME = "import-csv-to-conceptmap-command-test-input.csv";
private static String file;
private static String ourBase;
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu3();
private static int ourPort;
private static Server ourServer;
private static String ourVersion = "dstu3";
private static RestfulServer restfulServer;
private static HashMapResourceProviderConceptMapDstu3 hashMapResourceProviderConceptMapDstu3;
static {
System.setProperty("test", "true");
}
@After
public void afterClearResourceProvider() {
HashMapResourceProviderConceptMapDstu3 resourceProvider = (HashMapResourceProviderConceptMapDstu3) restfulServer.getResourceProviders().iterator().next();
resourceProvider.clear();
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler servletHandler = new ServletHandler();
restfulServer = new RestfulServer(ourCtx);
restfulServer.registerInterceptor(new VerboseLoggingInterceptor());
restfulServer.setResourceProviders(new HashMapResourceProviderConceptMapDstu3(ourCtx));
ServletHolder servletHolder = new ServletHolder(restfulServer);
servletHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(servletHandler);
ourServer.start();
ourBase = "http://localhost:" + ourPort;
ourClient = ourCtx.newRestfulGenericClient(ourBase);
}
@Test
public void testConditionalUpdateResultsInCreate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap();
String conceptMapUrl = conceptMap.getUrl();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = ourClient
.update()
.resource(conceptMap)
.conditional()
.where(ConceptMap.URL.matches().value(conceptMapUrl))
.execute();
assertEquals(Boolean.TRUE, methodOutcome.getCreated());
}
@Test
public void testConditionalUpdateResultsInUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap();
ourClient.create().resource(conceptMap).execute();
String conceptMapUrl = conceptMap.getUrl();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
MethodOutcome methodOutcome = ourClient
.update()
.resource(conceptMap)
.conditional()
.where(ConceptMap.URL.matches().value(conceptMapUrl))
.execute();
assertNull(methodOutcome.getCreated());
}
@Test
public void testNonConditionalUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandDstu3Test.createConceptMap();
ourClient.create().resource(conceptMap).execute();
Bundle response = ourClient
.search()
.forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL))
.returnBundle(Bundle.class)
.execute();
ConceptMap resultConceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
MethodOutcome methodOutcome = ourClient
.update()
.resource(resultConceptMap)
.withId(resultConceptMap.getIdElement())
.execute();
assertNull(methodOutcome.getCreated());
}
@Test
public void testImportCsvToConceptMapCommand() throws FHIRException {
ClassLoader classLoader = getClass().getClassLoader();
File fileToImport = new File(classLoader.getResource(FILENAME).getFile());
ImportCsvToConceptMapCommandDstu3Test.file = fileToImport.getAbsolutePath();
App.main(new String[] {"import-csv-to-conceptmap",
"-v", ourVersion,
"-t", ourBase,
"-u", CM_URL,
"-i", VS_URL_1,
"-o", VS_URL_2,
"-f", file,
"-l"});
Bundle response = ourClient
.search()
.forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL))
.returnBundle(Bundle.class)
.execute();
ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap));
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/1", conceptMap.getId());
assertEquals(CM_URL, conceptMap.getUrl());
assertEquals(VS_URL_1, conceptMap.getSourceUriType().getValueAsString());
assertEquals(VS_URL_2, conceptMap.getTargetUriType().getValueAsString());
assertEquals(3, conceptMap.getGroup().size());
ConceptMapGroupComponent group = conceptMap.getGroup().get(0);
assertEquals(CS_URL_1, group.getSource());
assertEquals("Version 1s", group.getSourceVersion());
assertEquals(CS_URL_2, group.getTarget());
assertEquals("Version 2t", group.getTargetVersion());
assertEquals(4, group.getElement().size());
SourceElementComponent source = group.getElement().get(0);
assertEquals("Code 1a", source.getCode());
assertEquals("Display 1a", source.getDisplay());
assertEquals(1, source.getTarget().size());
TargetElementComponent target = source.getTarget().get(0);
assertEquals("Code 2a", target.getCode());
assertEquals("Display 2a", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("2a This is a comment.", target.getComment());
source = group.getElement().get(1);
assertEquals("Code 1b", source.getCode());
assertEquals("Display 1b", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 2b", target.getCode());
assertEquals("Display 2b", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("2b This is a comment.", target.getComment());
source = group.getElement().get(2);
assertEquals("Code 1c", source.getCode());
assertEquals("Display 1c", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 2c", target.getCode());
assertEquals("Display 2c", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("2c This is a comment.", target.getComment());
source = group.getElement().get(3);
assertEquals("Code 1d", source.getCode());
assertEquals("Display 1d", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 2d", target.getCode());
assertEquals("Display 2d", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("2d This is a comment.", target.getComment());
group = conceptMap.getGroup().get(1);
assertEquals(CS_URL_1, group.getSource());
assertEquals("Version 1s", group.getSourceVersion());
assertEquals(CS_URL_3, group.getTarget());
assertEquals("Version 3t", group.getTargetVersion());
assertEquals(4, group.getElement().size());
source = group.getElement().get(0);
assertEquals("Code 1a", source.getCode());
assertEquals("Display 1a", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3a", target.getCode());
assertEquals("Display 3a", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3a This is a comment.", target.getComment());
source = group.getElement().get(1);
assertEquals("Code 1b", source.getCode());
assertEquals("Display 1b", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3b", target.getCode());
assertEquals("Display 3b", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3b This is a comment.", target.getComment());
source = group.getElement().get(2);
assertEquals("Code 1c", source.getCode());
assertEquals("Display 1c", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3c", target.getCode());
assertEquals("Display 3c", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3c This is a comment.", target.getComment());
source = group.getElement().get(3);
assertEquals("Code 1d", source.getCode());
assertEquals("Display 1d", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3d", target.getCode());
assertEquals("Display 3d", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3d This is a comment.", target.getComment());
group = conceptMap.getGroup().get(2);
assertEquals(CS_URL_2, group.getSource());
assertEquals("Version 2s", group.getSourceVersion());
assertEquals(CS_URL_3, group.getTarget());
assertEquals("Version 3t", group.getTargetVersion());
assertEquals(4, group.getElement().size());
source = group.getElement().get(0);
assertEquals("Code 2a", source.getCode());
assertEquals("Display 2a", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3a", target.getCode());
assertEquals("Display 3a", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3a This is a comment.", target.getComment());
source = group.getElement().get(1);
assertEquals("Code 2b", source.getCode());
assertEquals("Display 2b", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3b", target.getCode());
assertEquals("Display 3b", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3b This is a comment.", target.getComment());
source = group.getElement().get(2);
assertEquals("Code 2c", source.getCode());
assertEquals("Display 2c", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3c", target.getCode());
assertEquals("Display 3c", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3c This is a comment.", target.getComment());
source = group.getElement().get(3);
assertEquals("Code 2d", source.getCode());
assertEquals("Display 2d", source.getDisplay());
assertEquals(1, source.getTarget().size());
target = source.getTarget().get(0);
assertEquals("Code 3d", target.getCode());
assertEquals("Display 3d", target.getDisplay());
assertEquals(ConceptMapEquivalence.EQUAL, target.getEquivalence());
assertEquals("3d This is a comment.", target.getComment());
App.main(new String[] {"import-csv-to-conceptmap",
"-v", ourVersion,
"-t", ourBase,
"-u", CM_URL,
"-i", VS_URL_1,
"-o", VS_URL_2,
"-f", file,
"-l"});
response = ourClient
.search()
.forResource(ConceptMap.class)
.where(ConceptMap.URL.matches().value(CM_URL))
.returnBundle(Bundle.class)
.execute();
conceptMap = (ConceptMap) response.getEntryFirstRep().getResource();
assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/2", conceptMap.getId());
}
}

View File

@ -10,13 +10,13 @@ import ca.uhn.fhir.util.TestUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence;
import org.hl7.fhir.exceptions.FHIRException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@ -26,8 +26,8 @@ import java.io.File;
import static org.junit.Assert.*;
public class ImportCsvToConceptMapCommandTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommandTest.class);
public class ImportCsvToConceptMapCommandR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ImportCsvToConceptMapCommandR4Test.class);
private static final String CM_URL = "http://example.com/conceptmap";
private static final String VS_URL_1 = "http://example.com/valueset/1";
private static final String VS_URL_2 = "http://example.com/valueset/2";
@ -42,6 +42,7 @@ public class ImportCsvToConceptMapCommandTest {
private static FhirContext ourCtx = FhirContext.forR4();
private static int ourPort;
private static Server ourServer;
private static String ourVersion = "r4";
private static RestfulServer restfulServer;
@ -87,7 +88,7 @@ public class ImportCsvToConceptMapCommandTest {
@Test
public void testConditionalUpdateResultsInCreate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandTest.createConceptMap();
ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap();
String conceptMapUrl = conceptMap.getUrl();
ourLog.info("Searching for existing ConceptMap with specified URL (i.e. ConceptMap.url): {}", conceptMapUrl);
@ -104,7 +105,7 @@ public class ImportCsvToConceptMapCommandTest {
@Test
public void testConditionalUpdateResultsInUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandTest.createConceptMap();
ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap();
ourClient.create().resource(conceptMap).execute();
String conceptMapUrl = conceptMap.getUrl();
@ -122,7 +123,7 @@ public class ImportCsvToConceptMapCommandTest {
@Test
public void testNonConditionalUpdate() {
ConceptMap conceptMap = ExportConceptMapToCsvCommandTest.createConceptMap();
ConceptMap conceptMap = ExportConceptMapToCsvCommandR4Test.createConceptMap();
ourClient.create().resource(conceptMap).execute();
Bundle response = ourClient
@ -150,10 +151,10 @@ public class ImportCsvToConceptMapCommandTest {
public void testImportCsvToConceptMapCommand() throws FHIRException {
ClassLoader classLoader = getClass().getClassLoader();
File fileToImport = new File(classLoader.getResource(FILENAME).getFile());
ImportCsvToConceptMapCommandTest.file = fileToImport.getAbsolutePath();
ImportCsvToConceptMapCommandR4Test.file = fileToImport.getAbsolutePath();
App.main(new String[] {"import-csv-to-conceptmap",
"-v", "r4",
"-v", ourVersion,
"-t", ourBase,
"-u", CM_URL,
"-i", VS_URL_1,
@ -349,7 +350,7 @@ public class ImportCsvToConceptMapCommandTest {
assertEquals("3d This is a comment.", target.getComment());
App.main(new String[] {"import-csv-to-conceptmap",
"-v", "r4",
"-v", ourVersion,
"-t", ourBase,
"-u", CM_URL,
"-i", VS_URL_1,

View File

@ -1338,7 +1338,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@PostConstruct
public void start() {
ourLog.info("Starting resource DAO for type: {}", getResourceName());
ourLog.debug("Starting resource DAO for type: {}", getResourceName());
}
}

View File

@ -257,7 +257,7 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
@Transactional(propagation = Propagation.NEVER)
public Integer performReindexingPass(final Integer theCount) {
if (!myReindexLock.tryLock()) {
return null;
return -1;
}
try {
return doPerformReindexingPass(theCount);

View File

@ -27,7 +27,7 @@ import org.springframework.data.repository.query.Param;
*/
public interface ITermConceptMapGroupDao extends JpaRepository<TermConceptMapGroup, Long> {
@Query("DELETE FROM TermConceptMapGroup g WHERE g.myConceptMap.myId = :pid")
@Query("DELETE FROM TermConceptMapGroup g WHERE g.myId = :pid")
@Modifying
void deleteTermConceptMapGroupById(@Param("pid") Long theId);
}

View File

@ -27,7 +27,7 @@ import org.springframework.data.repository.query.Param;
*/
public interface ITermConceptMapGroupElementDao extends JpaRepository<TermConceptMapGroupElement, Long> {
@Query("DELETE FROM TermConceptMapGroupElement e WHERE e.myConceptMapGroup.myConceptMap.myId = :pid")
@Query("DELETE FROM TermConceptMapGroupElement e WHERE e.myId = :pid")
@Modifying
void deleteTermConceptMapGroupElementById(@Param("pid") Long theId);
}

View File

@ -27,7 +27,7 @@ import org.springframework.data.repository.query.Param;
*/
public interface ITermConceptMapGroupElementTargetDao extends JpaRepository<TermConceptMapGroupElementTarget, Long> {
@Query("DELETE FROM TermConceptMapGroupElementTarget t WHERE t.myConceptMapGroupElement.myConceptMapGroup.myConceptMap.myId = :pid")
@Query("DELETE FROM TermConceptMapGroupElementTarget t WHERE t.myId = :pid")
@Modifying
void deleteTermConceptMapGroupElementTargetById(@Param("pid") Long theId);
}

View File

@ -115,11 +115,12 @@ public class TermConceptMap implements Serializable {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("myId", myId)
.append("myResource", myResource.toString())
.append(myResource != null ? ("myResource=" + myResource.toString()) : ("myResource=(null)"))
.append("myResourcePid", myResourcePid)
.append("mySource", mySource)
.append("myTarget", myTarget)
.append("myUrl", myUrl)
.append("myConceptMapGroups - size", myConceptMapGroups.size())
.append(myConceptMapGroups != null ? ("myConceptMapGroups - size=" + myConceptMapGroups.size()) : ("myConceptMapGroups=(null)"))
.toString();
}
}

View File

@ -137,12 +137,12 @@ public class TermConceptMapGroup implements Serializable {
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("myId", myId)
.append("myConceptMap - id", myConceptMap.getId())
.append(myConceptMap != null ? ("myConceptMap - id=" + myConceptMap.getId()) : ("myConceptMap=(null)"))
.append("mySource", mySource)
.append("mySourceVersion", mySourceVersion)
.append("myTarget", myTarget)
.append("myTargetVersion", myTargetVersion)
.append("myConceptMapGroupElements - size", myConceptMapGroupElements.size())
.append(myConceptMapGroupElements != null ? ("myConceptMapGroupElements - size=" + myConceptMapGroupElements.size()) : ("myConceptMapGroupElements=(null)"))
.append("myConceptMapUrl", this.getConceptMapUrl())
.append("mySourceValueSet", this.getSourceValueSet())
.append("myTargetValueSet", this.getTargetValueSet())

View File

@ -151,10 +151,10 @@ public class TermConceptMapGroupElement implements Serializable {
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("myId", myId)
.append("myConceptMapGroup - id", myConceptMapGroup.getId())
.append(myConceptMapGroup != null ? ("myConceptMapGroup - id=" + myConceptMapGroup.getId()) : ("myConceptMapGroup=(null)"))
.append("myCode", myCode)
.append("myDisplay", myDisplay)
.append("myConceptMapGroupElementTargets - size", myConceptMapGroupElementTargets.size())
.append(myConceptMapGroupElementTargets != null ? ("myConceptMapGroupElementTargets - size=" + myConceptMapGroupElementTargets.size()) : ("myConceptMapGroupElementTargets=(null)"))
.append("myConceptMapUrl", this.getConceptMapUrl())
.append("mySystem", this.getSystem())
.append("mySystemVersion", this.getSystemVersion())

View File

@ -153,7 +153,7 @@ public class TermConceptMapGroupElementTarget implements Serializable {
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("myId", myId)
.append("myConceptMapGroupElement - id", myConceptMapGroupElement.getId())
.append(myConceptMapGroupElement != null ? ("myConceptMapGroupElement - id=" + myConceptMapGroupElement.getId()) : ("myConceptMapGroupElement=(null)"))
.append("myCode", myCode)
.append("myDisplay", myDisplay)
.append("myEquivalence", myEquivalence.toCode())

View File

@ -964,20 +964,33 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
termConceptMap.setUrl(theConceptMap.getUrl());
// Get existing entity so it can be deleted.
Optional<TermConceptMap> optionalExistingTermConceptMapById = myConceptMapDao.findTermConceptMapByResourcePid(termConceptMap.getResourcePid());
Optional<TermConceptMap> optionalExistingTermConceptMapById = myConceptMapDao.findTermConceptMapByResourcePid(theResourceTable.getId());
/*
* For now we always delete old versions. At some point, it would be nice to allow configuration to keep old versions.
*/
if (optionalExistingTermConceptMapById.isPresent()) {
Long id = optionalExistingTermConceptMapById.get().getId();
ourLog.info("Deleting existing TermConceptMap {} and its children...", id);
myConceptMapGroupElementTargetDao.deleteTermConceptMapGroupElementTargetById(id);
myConceptMapGroupElementDao.deleteTermConceptMapGroupElementById(id);
myConceptMapGroupDao.deleteTermConceptMapGroupById(id);
myConceptMapDao.deleteTermConceptMapById(id);
ourLog.info("Done deleting existing TermConceptMap {} and its children.", id);
TermConceptMap existingTermConceptMap = optionalExistingTermConceptMapById.get();
ourLog.info("Deleting existing TermConceptMap {} and its children...", existingTermConceptMap.getId());
for (TermConceptMapGroup group : existingTermConceptMap.getConceptMapGroups()) {
for (TermConceptMapGroupElement element : group.getConceptMapGroupElements()) {
for (TermConceptMapGroupElementTarget target : element.getConceptMapGroupElementTargets()) {
myConceptMapGroupElementTargetDao.deleteTermConceptMapGroupElementTargetById(target.getId());
}
myConceptMapGroupElementDao.deleteTermConceptMapGroupElementById(element.getId());
}
myConceptMapGroupDao.deleteTermConceptMapGroupById(group.getId());
}
myConceptMapDao.deleteTermConceptMapById(existingTermConceptMap.getId());
ourLog.info("Done deleting existing TermConceptMap {} and its children.", existingTermConceptMap.getId());
ourLog.info("Flushing...");
myConceptMapGroupElementTargetDao.flush();

View File

@ -47,7 +47,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou
* See #778
*/
@Test
public void testReadingObservationAccessRight() throws IOException {
public void testReadingObservationAccessRight() {
Practitioner practitioner1 = new Practitioner();
final IIdType practitionerId1 = ourClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless();
@ -105,7 +105,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou
* See #667
*/
@Test
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException {
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() {
Patient pt1 = new Patient();
pt1.setActive(true);
final IIdType pid1 = ourClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless();

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent;
@ -29,6 +30,37 @@ public class ResourceProviderDstu3ConceptMapTest extends BaseResourceProviderDst
myConceptMapId = myConceptMapDao.create(createConceptMap(), mySrd).getId().toUnqualifiedVersionless();
}
@Test
public void testStoreExistingTermConceptMapAndChildren() {
ConceptMap conceptMap = createConceptMap();
MethodOutcome methodOutcome = ourClient
.update()
.resource(conceptMap)
.conditional()
.where(ConceptMap.URL.matches().value(conceptMap.getUrl()))
.execute();
assertNull(methodOutcome.getCreated());
assertEquals("1", methodOutcome.getId().getVersionIdPart());
}
@Test
public void testStoreUpdatedTermConceptMapAndChildren() {
ConceptMap conceptMap = createConceptMap();
conceptMap.getGroupFirstRep().getElementFirstRep().setCode("UPDATED_CODE");
MethodOutcome methodOutcome = ourClient
.update()
.resource(conceptMap)
.conditional()
.where(ConceptMap.URL.matches().value(conceptMap.getUrl()))
.execute();
assertNull(methodOutcome.getCreated());
assertEquals("2", methodOutcome.getId().getVersionIdPart());
}
@Test
public void testTranslateByCodeSystemsAndSourceCodeOneToMany() {
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);

View File

@ -44,7 +44,7 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
* See #778
*/
@Test
public void testReadingObservationAccessRight() throws IOException {
public void testReadingObservationAccessRight() {
Practitioner practitioner1 = new Practitioner();
final IIdType practitionerId1 = myClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless();
@ -102,7 +102,7 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
* See #667
*/
@Test
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() throws IOException {
public void testBlockUpdatingPatientUserDoesnNotHaveAccessTo() {
Patient pt1 = new Patient();
pt1.setActive(true);
final IIdType pid1 = myClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless();

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
@ -29,6 +30,37 @@ public class ResourceProviderR4ConceptMapTest extends BaseResourceProviderR4Test
myConceptMapId = myConceptMapDao.create(createConceptMap(), mySrd).getId().toUnqualifiedVersionless();
}
@Test
public void testStoreExistingTermConceptMapAndChildren() {
ConceptMap conceptMap = createConceptMap();
MethodOutcome methodOutcome = myClient
.update()
.resource(conceptMap)
.conditional()
.where(ConceptMap.URL.matches().value(conceptMap.getUrl()))
.execute();
assertNull(methodOutcome.getCreated());
assertEquals("1", methodOutcome.getId().getVersionIdPart());
}
@Test
public void testStoreUpdatedTermConceptMapAndChildren() {
ConceptMap conceptMap = createConceptMap();
conceptMap.getGroupFirstRep().getElementFirstRep().setCode("UPDATED_CODE");
MethodOutcome methodOutcome = myClient
.update()
.resource(conceptMap)
.conditional()
.where(ConceptMap.URL.matches().value(conceptMap.getUrl()))
.execute();
assertNull(methodOutcome.getCreated());
assertEquals("2", methodOutcome.getId().getVersionIdPart());
}
@Test
public void testTranslateByCodeSystemsAndSourceCodeOneToMany() {
ConceptMap conceptMap = myConceptMapDao.read(myConceptMapId);

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import java.util.Collection;
/**
* @see AuthorizationInterceptor#setFlags(Collection)
*/
public enum AuthorizationFlagsEnum {
/**
* If this flag is set, attempts to perform read operations
* (read/search/history) will be matched by the interceptor before
* the method handler is called.
* <p>
* For example, suppose a rule set is in place that only allows read
* access to compartment <code>Patient/123</code>. With this flag set,
* any attempts
* to perform a FHIR read/search/history operation will be permitted
* to proceed to the method handler, and responses will be blocked
* by the AuthorizationInterceptor if the response contains a resource
* that is not in the given compartment.
* </p>
* <p>
* Setting this flag is less secure, since the interceptor can potentially leak
* information about the existence of data, but it is useful in some
* scenarios.
* </p>
*
* @since This flag has existed since HAPI FHIR 3.5.0. Prior to this
* version, this flag was the default and there was no ability to
* proactively block compartment read access.
*/
NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS;
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -28,6 +28,7 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import ca.uhn.fhir.util.CoverageIgnore;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -35,12 +36,12 @@ import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.defaultString;
@ -56,9 +57,10 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
*/
public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter implements IRuleApplier {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AuthorizationInterceptor.class);
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptor.class);
private PolicyEnum myDefaultPolicy = PolicyEnum.DENY;
private Set<AuthorizationFlagsEnum> myFlags = Collections.emptySet();
/**
* Constructor
@ -92,11 +94,12 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId,
IBaseResource theOutputResource) {
List<IAuthRule> rules = buildRuleList(theRequestDetails);
Set<AuthorizationFlagsEnum> flags = getFlags();
ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation);
Verdict verdict = null;
for (IAuthRule nextRule : rules) {
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, this);
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, this, flags);
if (verdict != null) {
ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision());
break;
@ -105,7 +108,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
if (verdict == null) {
ourLog.trace("No rules returned a decision, applying default {}", myDefaultPolicy);
return new Verdict(myDefaultPolicy, null);
return new Verdict(getDefaultPolicy(), null);
}
return verdict;
@ -206,6 +209,28 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
myDefaultPolicy = theDefaultPolicy;
}
/**
* This property configures any flags affecting how authorization is
* applied. By default no flags are applied.
*
* @see #setFlags(Collection)
*/
public Set<AuthorizationFlagsEnum> getFlags() {
return Collections.unmodifiableSet(myFlags);
}
/**
* This property configures any flags affecting how authorization is
* applied. By default no flags are applied.
*
* @param theFlags The flags (must not be null)
* @see #setFlags(Collection)
*/
public AuthorizationInterceptor setFlags(AuthorizationFlagsEnum... theFlags) {
Validate.notNull(theFlags, "theFlags must not be null");
return setFlags(Lists.newArrayList(theFlags));
}
/**
* Handle an access control verdict of {@link PolicyEnum#DENY}.
* <p>
@ -325,6 +350,19 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
handleUserOperation(theRequest, theNewResource, RestOperationTypeEnum.UPDATE);
}
/**
* This property configures any flags affecting how authorization is
* applied. By default no flags are applied.
*
* @param theFlags The flags (must not be null)
* @see #setFlags(AuthorizationFlagsEnum...)
*/
public AuthorizationInterceptor setFlags(Collection<AuthorizationFlagsEnum> theFlags) {
Validate.notNull(theFlags, "theFlags must not be null");
myFlags = new HashSet<>(theFlags);
return this;
}
private static UnsupportedOperationException failForDstu1() {
return new UnsupportedOperationException("Use of this interceptor on DSTU1 servers is not supportd");
}
@ -333,7 +371,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
if (theResponseObject == null) {
return Collections.emptyList();
}
List<IBaseResource> retVal;
boolean isContainer = false;
@ -342,11 +380,11 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
} else if (theResponseObject instanceof IBaseParameters) {
isContainer = true;
}
if (!isContainer) {
return Collections.singletonList(theResponseObject);
}
retVal = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class);
// Exclude the container

View File

@ -27,6 +27,15 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
import java.util.Set;
/**
* Note: At this time, this interface is considered internal API to HAPI FHIR,
* and is subject to change without warning. Create your own implementations at
* your own risk. If you have use cases that are not met by the current
* implementation, please consider raising them on the HAPI FHIR
* Google Group.
*/
public interface IAuthRule {
/**
@ -44,9 +53,10 @@ public interface IAuthRule {
* @param theRuleApplier
* The rule applying module (this can be used by rules to apply the rule set to
* nested objects in the request, such as nested requests in a transaction)
* @param theFlags
* @return Returns a policy decision, or <code>null</code> if the rule does not apply
*/
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier);
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags);
/**
* Returns a name for this rule, to be used in logs and error messages

View File

@ -29,10 +29,11 @@ import org.hl7.fhir.instance.model.api.IIdType;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class OperationRule extends BaseRule implements IAuthRule {
private RuleBuilder.ITenantApplicabilityChecker myTenentApplicabilityChecker;
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
private String myOperationName;
private boolean myAppliesToServer;
private HashSet<Class<? extends IBaseResource>> myAppliesToTypes;
@ -75,17 +76,25 @@ class OperationRule extends BaseRule implements IAuthRule {
}
@Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
if (myTenentApplicabilityChecker != null) {
if (!myTenentApplicabilityChecker.applies(theRequestDetails)) {
if (myTenantApplicabilityChecker != null) {
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
return null;
}
}
boolean applies = false;
switch (theOperation) {
case ADD_TAGS:
case DELETE_TAGS:
case GET_TAGS:
case GET_PAGE:
case GRAPHQL_REQUEST:
// These things can't be tracked by the AuthorizationInterceptor
// at this time
return null;
case EXTENDED_OPERATION_SERVER:
if (myAppliesToServer || myAppliesAtAnyLevel) {
applies = true;
@ -130,6 +139,40 @@ class OperationRule extends BaseRule implements IAuthRule {
}
}
break;
case CREATE:
break;
case DELETE:
break;
case HISTORY_INSTANCE:
break;
case HISTORY_SYSTEM:
break;
case HISTORY_TYPE:
break;
case READ:
break;
case SEARCH_SYSTEM:
break;
case SEARCH_TYPE:
break;
case TRANSACTION:
break;
case UPDATE:
break;
case VALIDATE:
break;
case VREAD:
break;
case METADATA:
break;
case META_ADD:
break;
case META:
break;
case META_DELETE:
break;
case PATCH:
break;
default:
return null;
}
@ -160,8 +203,8 @@ class OperationRule extends BaseRule implements IAuthRule {
myOperationName = theOperationName;
}
public void setTenentApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenentApplicabilityChecker) {
myTenentApplicabilityChecker = theTenentApplicabilityChecker;
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
}
}

View File

@ -174,7 +174,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
myOpRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
}
if (myOperationRule != null) {
myOperationRule.setTenentApplicabilityChecker(myTenantApplicabilityChecker);
myOperationRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
}
}

View File

@ -41,7 +41,7 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
@Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
IRuleApplier theRuleApplier) {
IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
if (theInputResourceId != null) {
return null;

View File

@ -1,5 +1,30 @@
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.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
import ca.uhn.fhir.util.FhirTerser;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
@ -11,9 +36,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,26 +47,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L%
*/
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
import ca.uhn.fhir.util.FhirTerser;
class RuleImplOp extends BaseRule /* implements IAuthRule */ {
private AppliesTypeEnum myAppliesTo;
@ -54,13 +59,16 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
private List<IIdType> myAppliesToInstances;
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
/**
* Constructor
*/
public RuleImplOp(String theRuleName) {
super(theRuleName);
}
@Override
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
IRuleApplier theRuleApplier) {
IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
if (myTenantApplicabilityChecker != null) {
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
@ -73,232 +81,335 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
IBaseResource appliesToResource;
IIdType appliesToResourceId = null;
String appliesToResourceType = null;
Map<String, String[]> appliesToSearchParams = null;
switch (myOp) {
case READ:
if (theOutputResource == null) {
switch (theOperation) {
case READ:
case VREAD:
appliesToResourceId = theInputResourceId;
appliesToResourceType = theInputResourceId.getResourceType();
break;
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_INSTANCE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case READ:
if (theOutputResource == null) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.ALLOW, this);
default:
return null;
switch (theOperation) {
case READ:
case VREAD:
appliesToResourceId = theInputResourceId;
appliesToResourceType = theInputResourceId.getResourceType();
break;
case SEARCH_SYSTEM:
case HISTORY_SYSTEM:
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
return new Verdict(PolicyEnum.ALLOW, this);
}
break;
case SEARCH_TYPE:
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
return new Verdict(PolicyEnum.ALLOW, this);
}
appliesToResourceType = theRequestDetails.getResourceName();
appliesToSearchParams = theRequestDetails.getParameters();
break;
case HISTORY_TYPE:
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
return new Verdict(PolicyEnum.ALLOW, this);
}
appliesToResourceType = theRequestDetails.getResourceName();
break;
case HISTORY_INSTANCE:
if (theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
return new Verdict(PolicyEnum.ALLOW, this);
}
appliesToResourceId = theInputResourceId;
break;
case GET_PAGE:
return new Verdict(PolicyEnum.ALLOW, this);
// None of the following are checked on the way in
case ADD_TAGS:
case DELETE_TAGS:
case GET_TAGS:
case GRAPHQL_REQUEST:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:
case EXTENDED_OPERATION_INSTANCE:
case CREATE:
case DELETE:
case TRANSACTION:
case UPDATE:
case VALIDATE:
case METADATA:
case META_ADD:
case META:
case META_DELETE:
case PATCH:
default:
return null;
}
}
appliesToResource = theOutputResource;
if (theOutputResource != null) {
appliesToResourceId = theOutputResource.getIdElement();
}
}
appliesToResource = theOutputResource;
if (theOutputResource != null) {
appliesToResourceId = theOutputResource.getIdElement();
}
break;
case WRITE:
if (theInputResource == null && theInputResourceId == null) {
return null;
}
switch (theOperation) {
case CREATE:
case UPDATE:
case ADD_TAGS:
case DELETE_TAGS:
case META_ADD:
case META_DELETE:
case PATCH:
appliesToResource = theInputResource;
appliesToResourceId = theInputResourceId;
break;
default:
return null;
}
break;
case DELETE:
if (theOperation == RestOperationTypeEnum.DELETE) {
if (theInputResource == null) {
return newVerdict();
}
appliesToResource = theInputResource;
} else {
return null;
}
break;
case BATCH:
case TRANSACTION:
if (!(theOperation == RestOperationTypeEnum.TRANSACTION)) {
return null;
}
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
if (getMode() == PolicyEnum.DENY) {
return new Verdict(PolicyEnum.DENY, this);
}
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
Verdict verdict = null;
for (BundleEntryParts nextPart : inputResources) {
IBaseResource inputResource = nextPart.getResource();
RestOperationTypeEnum operation = null;
if (nextPart.getRequestType() == RequestTypeEnum.GET) {
continue;
}
if (nextPart.getRequestType() == RequestTypeEnum.POST) {
operation = RestOperationTypeEnum.CREATE;
} else if (nextPart.getRequestType() == RequestTypeEnum.PUT) {
operation = RestOperationTypeEnum.UPDATE;
} else {
throw new InvalidRequestException("Can not handle transaction with operation of type " + nextPart.getRequestType());
}
/*
* This is basically just being conservative - Be careful of transactions containing
* nested operations and nested transactions. We block the by default. At some point
* it would be nice to be more nuanced here.
*/
RuntimeResourceDefinition resourceDef = ctx.getResourceDefinition(nextPart.getResource());
if ("Parameters".equals(resourceDef.getName()) || "Bundle".equals(resourceDef.getName())) {
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null, null);
if (newVerdict == null) {
continue;
} else if (verdict == null) {
verdict = newVerdict;
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
verdict = newVerdict;
}
}
return verdict;
} else if (theOutputResource != null) {
List<IBaseResource> outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(theOutputResource, theRequestDetails.getFhirContext());
Verdict verdict = null;
for (IBaseResource nextResource : outputResources) {
if (nextResource == null) {
continue;
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextResource);
if (newVerdict == null) {
continue;
} else if (verdict == null) {
verdict = newVerdict;
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
verdict = newVerdict;
}
}
return verdict;
} else {
return null;
}
case ALLOW_ALL:
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.ALLOW, this);
case DENY_ALL:
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.DENY, this);
case METADATA:
if (theOperation == RestOperationTypeEnum.METADATA) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
case WRITE:
if (theInputResource == null && theInputResourceId == null) {
return null;
}
return newVerdict();
}
return null;
default:
// Should not happen
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
}
switch (theOperation) {
case CREATE:
case UPDATE:
case ADD_TAGS:
case DELETE_TAGS:
case META_ADD:
case META_DELETE:
case PATCH:
appliesToResource = theInputResource;
appliesToResourceId = theInputResourceId;
break;
default:
return null;
}
break;
case DELETE:
if (theOperation == RestOperationTypeEnum.DELETE) {
if (theInputResource == null) {
return newVerdict();
}
appliesToResource = theInputResource;
} else {
return null;
}
break;
case BATCH:
case TRANSACTION:
if (!(theOperation == RestOperationTypeEnum.TRANSACTION)) {
return null;
}
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
if (getMode() == PolicyEnum.DENY) {
return new Verdict(PolicyEnum.DENY, this);
}
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
Verdict verdict = null;
for (BundleEntryParts nextPart : inputResources) {
switch (myAppliesTo) {
case INSTANCES:
if (appliesToResourceId != null) {
for (IIdType next : myAppliesToInstances) {
if (isNotBlank(next.getResourceType())) {
if (!next.getResourceType().equals(appliesToResourceId.getResourceType())) {
IBaseResource inputResource = nextPart.getResource();
RestOperationTypeEnum operation = null;
if (nextPart.getRequestType() == RequestTypeEnum.GET) {
continue;
}
if (nextPart.getRequestType() == RequestTypeEnum.POST) {
operation = RestOperationTypeEnum.CREATE;
} else if (nextPart.getRequestType() == RequestTypeEnum.PUT) {
operation = RestOperationTypeEnum.UPDATE;
} else {
throw new InvalidRequestException("Can not handle transaction with operation of type " + nextPart.getRequestType());
}
/*
* This is basically just being conservative - Be careful of transactions containing
* nested operations and nested transactions. We block the by default. At some point
* it would be nice to be more nuanced here.
*/
RuntimeResourceDefinition resourceDef = ctx.getResourceDefinition(nextPart.getResource());
if ("Parameters".equals(resourceDef.getName()) || "Bundle".equals(resourceDef.getName())) {
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null, null);
if (newVerdict == null) {
continue;
} else if (verdict == null) {
verdict = newVerdict;
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
verdict = newVerdict;
}
}
if (!next.getIdPart().equals(appliesToResourceId.getIdPart())) {
continue;
return verdict;
} else if (theOutputResource != null) {
List<IBaseResource> outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(theOutputResource, theRequestDetails.getFhirContext());
Verdict verdict = null;
for (IBaseResource nextResource : outputResources) {
if (nextResource == null) {
continue;
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextResource);
if (newVerdict == null) {
continue;
} else if (verdict == null) {
verdict = newVerdict;
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
verdict = newVerdict;
}
}
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return newVerdict();
return verdict;
} else {
return null;
}
}
return null;
case ALL_RESOURCES:
if (appliesToResourceType != null) {
case ALLOW_ALL:
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.ALLOW, this);
}
break;
case TYPES:
if (appliesToResource != null) {
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
case DENY_ALL:
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
}
if (appliesToResourceId != null && appliesToResourceId.hasResourceType()) {
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
if (myAppliesToTypes.contains(type) == false) {
return null;
}
}
if (appliesToResourceType != null) {
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceType).getImplementingClass();
if (myAppliesToTypes.contains(type)) {
return new Verdict(PolicyEnum.DENY, this);
case METADATA:
if (theOperation == RestOperationTypeEnum.METADATA) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.ALLOW, this);
return newVerdict();
}
}
break;
default:
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
return null;
default:
// Should not happen
throw new IllegalStateException("Unable to apply security to event of type " + theOperation);
}
switch (myAppliesTo) {
case INSTANCES:
if (appliesToResourceId != null) {
for (IIdType next : myAppliesToInstances) {
if (isNotBlank(next.getResourceType())) {
if (!next.getResourceType().equals(appliesToResourceId.getResourceType())) {
continue;
}
}
if (!next.getIdPart().equals(appliesToResourceId.getIdPart())) {
continue;
}
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return newVerdict();
}
}
return null;
case ALL_RESOURCES:
if (appliesToResourceType != null) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
return new Verdict(PolicyEnum.ALLOW, this);
}
}
break;
case TYPES:
if (appliesToResource != null) {
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
return null;
}
}
}
if (appliesToResourceId != null && appliesToResourceId.hasResourceType()) {
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
if (myAppliesToTypes.contains(type) == false) {
return null;
}
}
if (appliesToResourceType != null) {
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceType).getImplementingClass();
if (myAppliesToTypes.contains(type)) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
return new Verdict(PolicyEnum.ALLOW, this);
} else if (myClassifierType == ClassifierTypeEnum.IN_COMPARTMENT) {
// ok we'll check below
}
}
}
break;
default:
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
}
switch (myClassifierType) {
case ANY_ID:
break;
case IN_COMPARTMENT:
FhirTerser t = ctx.newTerser();
boolean foundMatch = false;
for (IIdType next : myClassifierCompartmentOwners) {
if (appliesToResource != null) {
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesToResource, next)) {
foundMatch = true;
break;
case ANY_ID:
break;
case IN_COMPARTMENT:
FhirTerser t = ctx.newTerser();
boolean foundMatch = false;
for (IIdType next : myClassifierCompartmentOwners) {
if (appliesToResource != null) {
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesToResource, next)) {
foundMatch = true;
break;
}
}
if (appliesToResourceId != null && appliesToResourceId.hasResourceType() && appliesToResourceId.hasIdPart()) {
if (appliesToResourceId.toUnqualifiedVersionless().getValue().equals(next.toUnqualifiedVersionless().getValue())) {
foundMatch = true;
break;
}
}
/*
* If the client has permission to read compartment
* Patient/ABC, then a search for Patient?_id=Patient/ABC
* should be permitted. This is kind of a one-off case, but
* it makes sense.
*/
if (next.getResourceType().equals(appliesToResourceType)) {
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(appliesToSearchParams, next, IAnyResource.SP_RES_ID);
if (verdict != null) {
return verdict;
}
}
/*
* If we're trying to read a resource that could potentially be
* in the given compartment, we'll let the request through and
* catch any issues on the response.
*
* This is less than perfect, but it's the best we can do-
* If the user is allowed to see compartment "Patient/123" and
* the client is requesting to read a CarePlan, there is nothing
* in the request URL that indicates whether or not the CarePlan
* might be in the given compartment.
*/
if (isNotBlank(appliesToResourceType)) {
RuntimeResourceDefinition sourceDef = theRequestDetails.getFhirContext().getResourceDefinition(appliesToResourceType);
String compartmentOwnerResourceType = next.getResourceType();
if (!StringUtils.equals(appliesToResourceType, compartmentOwnerResourceType)) {
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(compartmentOwnerResourceType);
if (params.isEmpty() == false) {
/*
* If this is a search, we can at least check whether
* the client has requested a search parameter that
* would match the given compartment. In this case, this
* is a very effective mechanism.
*/
if (appliesToSearchParams != null && !theFlags.contains(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
for (RuntimeSearchParam nextRuntimeSearchParam : params) {
String name = nextRuntimeSearchParam.getName();
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(appliesToSearchParams, next, name);
if (verdict != null) {
return verdict;
}
}
} else {
return new Verdict(PolicyEnum.ALLOW, this);
}
break;
}
}
}
}
if (appliesToResourceId != null && appliesToResourceId.hasResourceType() && appliesToResourceId.hasIdPart()) {
if (appliesToResourceId.toUnqualifiedVersionless().getValue().equals(next.toUnqualifiedVersionless().getValue())) {
foundMatch = true;
break;
}
if (!foundMatch) {
return null;
}
}
if (!foundMatch) {
return null;
}
break;
default:
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
break;
default:
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
}
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
@ -308,22 +419,32 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
return newVerdict();
}
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
private Verdict checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(Map<String, String[]> theSearchParams, IIdType theCompartmentOwner, String theSearchParamName) {
Verdict verdict = null;
if (theSearchParams != null) {
String[] values = theSearchParams.get(theSearchParamName);
if (values != null) {
for (String nextParameterValue : values) {
if (nextParameterValue.equals(theCompartmentOwner.getValue())) {
verdict = new Verdict(PolicyEnum.ALLOW, this);
break;
}
if (nextParameterValue.equals(theCompartmentOwner.getIdPart())) {
verdict = new Verdict(PolicyEnum.ALLOW, this);
break;
}
}
}
}
return verdict;
}
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
builder.append("op", myOp);
builder.append("transactionAppliesToOp", myTransactionAppliesToOp);
builder.append("appliesTo", myAppliesTo);
builder.append("appliesToTypes", myAppliesToTypes);
builder.append("appliesToTenant", myTenantApplicabilityChecker);
builder.append("classifierCompartmentName", myClassifierCompartmentName);
builder.append("classifierCompartmentOwners", myClassifierCompartmentOwners);
builder.append("classifierType", myClassifierType);
return builder.toString();
public TransactionAppliesToEnum getTransactionAppliesToOp() {
return myTransactionAppliesToOp;
}
public void setTransactionAppliesToOp(TransactionAppliesToEnum theOp) {
myTransactionAppliesToOp = theOp;
}
private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) {
@ -334,23 +455,23 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
IBaseBundle request = (IBaseBundle) theInputResource;
String bundleType = BundleUtil.getBundleType(theContext, request);
switch (theOp) {
case TRANSACTION:
return "transaction".equals(bundleType);
case BATCH:
return "batch".equals(bundleType);
default:
return false;
case TRANSACTION:
return "transaction".equals(bundleType);
case BATCH:
return "batch".equals(bundleType);
default:
return false;
}
}
public TransactionAppliesToEnum getTransactionAppliesToOp() {
return myTransactionAppliesToOp;
}
public void setAppliesTo(AppliesTypeEnum theAppliesTo) {
myAppliesTo = theAppliesTo;
}
public void setAppliesToInstances(List<IIdType> theAppliesToInstances) {
myAppliesToInstances = theAppliesToInstances;
}
public void setAppliesToTypes(Set<?> theAppliesToTypes) {
myAppliesToTypes = theAppliesToTypes;
}
@ -372,12 +493,22 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
return this;
}
public void setTransactionAppliesToOp(TransactionAppliesToEnum theOp) {
myTransactionAppliesToOp = theOp;
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
}
public void setAppliesToInstances(List<IIdType> theAppliesToInstances) {
myAppliesToInstances = theAppliesToInstances;
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
builder.append("op", myOp);
builder.append("transactionAppliesToOp", myTransactionAppliesToOp);
builder.append("appliesTo", myAppliesTo);
builder.append("appliesToTypes", myAppliesToTypes);
builder.append("appliesToTenant", myTenantApplicabilityChecker);
builder.append("classifierCompartmentName", myClassifierCompartmentName);
builder.append("classifierCompartmentOwners", myClassifierCompartmentOwners);
builder.append("classifierType", myClassifierType);
return builder.toString();
}
}

View File

@ -15,6 +15,7 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.rest.param.ReferenceParam;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
@ -65,7 +66,7 @@ public class AuthorizationInterceptorDstu2Test {
@Before
public void before() {
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.NEVER);
for (IServerInterceptor next : new ArrayList<IServerInterceptor>(ourServlet.getInterceptors())) {
for (IServerInterceptor next : new ArrayList<>(ourServlet.getInterceptors())) {
ourServlet.unregisterInterceptor(next);
}
ourReturn = null;
@ -1166,8 +1167,8 @@ public class AuthorizationInterceptorDstu2Test {
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@ -1177,8 +1178,8 @@ public class AuthorizationInterceptorDstu2Test {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1"))
.build();
.allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdDt("Patient/1"))
.build();
}
});
@ -1187,13 +1188,13 @@ public class AuthorizationInterceptorDstu2Test {
String respString;
Bundle respBundle;
ourReturn = new ArrayList<IResource>();
ourReturn = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ourReturn.add(createPatient(1));
ourReturn.add(createObservation(i, "Patient/1"));
}
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -1201,7 +1202,7 @@ public class AuthorizationInterceptorDstu2Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal().intValue());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next"));
// Load next page
@ -1215,7 +1216,7 @@ public class AuthorizationInterceptorDstu2Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal().intValue());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertEquals("Observation/5", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNull(respBundle.getLink("next"));
}
@ -1226,8 +1227,8 @@ public class AuthorizationInterceptorDstu2Test {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdDt("Patient/1"))
.build();
.allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdDt("Patient/1"))
.build();
}
});
@ -1236,16 +1237,16 @@ public class AuthorizationInterceptorDstu2Test {
String respString;
Bundle respBundle;
ourReturn = new ArrayList<IResource>();
ourReturn = new ArrayList<>();
for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(1));
ourReturn.add(createObservation(i, "Patient/1"));
}
for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(2));
for (int i = 5; i < 10; i++) {
ourReturn.add(createObservation(i, "Patient/2"));
}
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -1253,7 +1254,7 @@ public class AuthorizationInterceptorDstu2Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal().intValue());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next"));
// Load next page
@ -1261,7 +1262,7 @@ public class AuthorizationInterceptorDstu2Test {
ourHitMethod = false;
httpGet = new HttpGet(respBundle.getLink("next").getUrl());
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
@ -1283,7 +1284,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpResponse status;
String response;
ourReturn = Arrays.asList(createPatient(2));
ourReturn = Collections.singletonList(createPatient(2));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpGet);
@ -1291,9 +1292,9 @@ public class AuthorizationInterceptorDstu2Test {
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
assertFalse(ourHitMethod);
ourReturn = Arrays.asList(createObservation(10, "Patient/2"));
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
status = ourClient.execute(httpGet);
@ -1303,7 +1304,7 @@ public class AuthorizationInterceptorDstu2Test {
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Arrays.asList(createCarePlan(10, "Patient/2"));
ourReturn = Collections.singletonList(createCarePlan(10, "Patient/2"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
status = ourClient.execute(httpGet);
@ -1321,7 +1322,7 @@ public class AuthorizationInterceptorDstu2Test {
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
assertFalse(ourHitMethod);
ourReturn = Arrays.asList(createPatient(2), createObservation(10, "Patient/1"));
ourHitMethod = false;
@ -1331,7 +1332,7 @@ public class AuthorizationInterceptorDstu2Test {
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
assertFalse(ourHitMethod);
}
@ -1359,7 +1360,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpPost httpPost;
HttpResponse status;
ourReturn = Arrays.asList((IResource) output);
ourReturn = Collections.singletonList(output);
ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
@ -1419,6 +1420,7 @@ public class AuthorizationInterceptorDstu2Test {
httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/1")));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
ourLog.debug(response);
assertEquals(201, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@ -1469,7 +1471,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpDelete httpDelete;
HttpResponse status;
ourReturn = Arrays.asList(createPatient(1));
ourReturn = Collections.singletonList(createPatient(1));
ourHitMethod = false;
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar");
@ -1497,7 +1499,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpDelete httpDelete;
HttpResponse status;
ourReturn = Arrays.asList(createPatient(1));
ourReturn = Collections.singletonList(createPatient(1));
ourHitMethod = false;
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar");
@ -1525,7 +1527,7 @@ public class AuthorizationInterceptorDstu2Test {
HttpResponse status;
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
ourReturn = Collections.singletonList(createPatient(2));
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpDelete);
extractResponseAndClose(status);
@ -1533,7 +1535,7 @@ public class AuthorizationInterceptorDstu2Test {
assertTrue(ourHitMethod);
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(1));
ourReturn = Collections.singletonList(createPatient(1));
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/1");
status = ourClient.execute(httpDelete);
extractResponseAndClose(status);
@ -1665,6 +1667,7 @@ public class AuthorizationInterceptorDstu2Test {
httpPost.setEntity(createFhirResourceEntity(createPatient(null)));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
ourLog.debug(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
@ -1703,6 +1706,7 @@ public class AuthorizationInterceptorDstu2Test {
httpPost.setEntity(createFhirResourceEntity(createPatient(null)));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
ourLog.debug(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
@ -1718,7 +1722,7 @@ public class AuthorizationInterceptorDstu2Test {
}
@Test
public void testInvalidInstanceIds() throws Exception {
public void testInvalidInstanceIds() {
try {
new RuleBuilder().allow("Rule 1").write().instance((String) null);
fail();
@ -2009,7 +2013,7 @@ public class AuthorizationInterceptorDstu2Test {
}
@Search()
public List<IResource> search() {
public List<IResource> search(@OptionalParam(name="subject")ReferenceParam theSubject) {
ourHitMethod = true;
return ourReturn;
}

View File

@ -32,6 +32,12 @@ public class FhirContextDstu3Test {
assertEquals(FhirVersionEnum.DSTU3, ctx.getVersion().getVersion());
}
@Test
public void testRuntimeSearchParamToString() {
String val = ourCtx.getResourceDefinition("Patient").getSearchParam("gender").toString();
assertEquals("RuntimeSearchParam[base=[Patient],name=gender,path=Patient.gender,id=<null>,uri=<null>]", val);
}
@Test
public void testCustomTypeDoesntBecomeDefault() {
FhirContext ctx = FhirContext.forDstu3();
@ -69,7 +75,7 @@ public class FhirContextDstu3Test {
final FhirContext ctx = FhirContext.forDstu3();
final int numThreads = 40;
final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<Throwable>());
final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<>());
final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
try {
final CountDownLatch threadsReady = new CountDownLatch(numThreads);
@ -77,19 +83,17 @@ public class FhirContextDstu3Test {
for (int i = 0; i < numThreads; i++) {
threadPool.submit(
new Runnable() {
public void run() {
threadsReady.countDown();
try {
threadsReady.await();
RuntimeResourceDefinition def = ctx.getResourceDefinition("patient");
ourLog.info(def.toString());
assertNotNull(def);
} catch (final Exception e) {
exceptions.add(e);
}
threadsFinished.countDown();
() -> {
threadsReady.countDown();
try {
threadsReady.await();
RuntimeResourceDefinition def = ctx.getResourceDefinition("patient");
ourLog.info(def.toString());
assertNotNull(def);
} catch (final Exception e) {
exceptions.add(e);
}
threadsFinished.countDown();
}
);
}
@ -108,18 +112,15 @@ public class FhirContextDstu3Test {
* See #794
*/
@Test
public void testInitializeThreadSafety2() throws InterruptedException {
public void testInitializeThreadSafety2() {
final FhirContext dstu3FhirContext = FhirContext.forDstu3();
final AtomicInteger count = new AtomicInteger();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
OperationOutcomeUtil.newInstance(dstu3FhirContext);
ourLog.info("Have finished {}", count.incrementAndGet());
}
new Thread(() -> {
OperationOutcomeUtil.newInstance(dstu3FhirContext);
ourLog.info("Have finished {}", count.incrementAndGet());
}).start();
}

View File

@ -8,9 +8,11 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.IRequestOperationCallback;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.ReferenceParam;
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.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.*;
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
@ -97,6 +99,16 @@ public class AuthorizationInterceptorR4Test {
return retVal;
}
private Resource createDiagnosticReport(Integer theId, String theSubjectId) {
DiagnosticReport retVal = new DiagnosticReport();
if (theId != null) {
retVal.setId(new IdType("DiagnosticReport", (long) theId));
}
retVal.getCode().setText("OBS");
retVal.setSubject(new Reference(theSubjectId));
return retVal;
}
private Organization createOrganization(int theIndex) {
Organization retVal = new Organization();
retVal.setId("" + theIndex);
@ -287,6 +299,68 @@ public class AuthorizationInterceptorR4Test {
}
@Test
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder().allow().read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/123")).andThen().denyAll()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
Patient patient;
CarePlan carePlan;
// Unqualified
patient = new Patient();
patient.setId("123");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Qualified
patient = new Patient();
patient.setId("Patient/123");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Wrong one
patient = new Patient();
patient.setId("456");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
/**
* #528
*/
@ -390,69 +464,6 @@ public class AuthorizationInterceptorR4Test {
assertTrue(ourHitMethod);
}
@Test
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder().allow().read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/123")).andThen().denyAll()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
Patient patient;
CarePlan carePlan;
// Unqualified
patient = new Patient();
patient.setId("123");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Qualified
patient = new Patient();
patient.setId("Patient/123");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Wrong one
patient = new Patient();
patient.setId("456");
carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
carePlan.getSubject().setResource(patient);
ourHitMethod = false;
ourReturn = Collections.singletonList(carePlan);
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@Test
public void testBatchWhenOnlyTransactionAllowed() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -477,7 +488,7 @@ public class AuthorizationInterceptorR4Test {
HttpPost httpPost;
HttpResponse status;
ourReturn = Collections.singletonList((Resource) output);
ourReturn = Collections.singletonList(output);
ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
@ -510,7 +521,7 @@ public class AuthorizationInterceptorR4Test {
HttpPost httpPost;
HttpResponse status;
ourReturn = Collections.singletonList((Resource) output);
ourReturn = Collections.singletonList(output);
ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
@ -544,7 +555,7 @@ public class AuthorizationInterceptorR4Test {
HttpResponse status;
String response;
ourReturn = Collections.singletonList((Resource) output);
ourReturn = Collections.singletonList(output);
ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
@ -1805,6 +1816,85 @@ public class AuthorizationInterceptorR4Test {
}
@Test
public void testReadByCompartmentReadByPatientParam() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
ourReturn = Collections.singletonList(createDiagnosticReport(1, "Patient/1"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/DiagnosticReport?patient=Patient/1");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createDiagnosticReport(1, "Patient/1"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/DiagnosticReport?patient=1");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createDiagnosticReport(1, "Patient/1"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/DiagnosticReport?patient=Patient/2");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testReadByCompartmentReadByIdParam() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
ourReturn = Collections.singletonList(createPatient(1));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=Patient/1");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createPatient(1));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=1");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=Patient/2");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testReadByCompartmentRight() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -1841,13 +1931,86 @@ public class AuthorizationInterceptorR4Test {
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testReadByCompartmentWrong() throws Exception {
public void testReadByCompartmentWrongAllTypesProactiveBlockEnabledNoResponse() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
.build();
}
}.setFlags());
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.emptyList();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(404, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(404, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/999/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testReadByCompartmentWrongProactiveBlockDisabled() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
@ -1856,7 +2019,7 @@ public class AuthorizationInterceptorR4Test {
.allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build();
}
});
}.setFlags(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS));
HttpGet httpGet;
HttpResponse status;
@ -1870,7 +2033,7 @@ public class AuthorizationInterceptorR4Test {
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
assertFalse(ourHitMethod);
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
ourHitMethod = false;
@ -1914,6 +2077,133 @@ public class AuthorizationInterceptorR4Test {
}
@Test
public void testReadByCompartmentWrongProactiveBlockDisabledNoResponse() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen()
.allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build();
}
}.setFlags(AuthorizationFlagsEnum.NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS));
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.emptyList();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(404, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@Test
public void testReadByCompartmentWrongProactiveBlockEnabledNoResponse() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen()
.allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build();
}
}.setFlags());
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.emptyList();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(404, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// CarePlan could potentially be in the Patient/1 compartment but we don't
// have any rules explicitly allowing CarePlan so it's blocked
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/999/_history");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testReadByInstance() throws Exception {
ourConditionalCreateId = "1";
@ -1965,7 +2255,7 @@ public class AuthorizationInterceptorR4Test {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1"))
.allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build();
}
});
@ -1977,11 +2267,11 @@ public class AuthorizationInterceptorR4Test {
ourReturn = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ourReturn.add(createPatient(1));
ourReturn.add(createObservation(i, "Patient/1"));
}
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -1989,7 +2279,7 @@ public class AuthorizationInterceptorR4Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next"));
// Load next page
@ -2003,7 +2293,7 @@ public class AuthorizationInterceptorR4Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertEquals("Observation/5", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNull(respBundle.getLink("next"));
}
@ -2014,7 +2304,7 @@ public class AuthorizationInterceptorR4Test {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1"))
.allow("Rule 1").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1"))
.build();
}
});
@ -2026,14 +2316,14 @@ public class AuthorizationInterceptorR4Test {
ourReturn = new ArrayList<>();
for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(1));
ourReturn.add(createObservation(i, "Patient/1"));
}
for (int i = 0; i < 5; i++) {
ourReturn.add(createPatient(2));
for (int i = 5; i < 10; i++) {
ourReturn.add(createObservation(i, "Patient/2"));
}
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json");
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?_count=5&_format=json&subject=Patient/1");
status = ourClient.execute(httpGet);
respString = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -2041,7 +2331,7 @@ public class AuthorizationInterceptorR4Test {
respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString);
assertEquals(5, respBundle.getEntry().size());
assertEquals(10, respBundle.getTotal());
assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertEquals("Observation/0", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue());
assertNotNull(respBundle.getLink("next"));
// Load next page
@ -2078,7 +2368,7 @@ public class AuthorizationInterceptorR4Test {
Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output);
ourReturn = Collections.singletonList(output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost);
@ -2111,7 +2401,7 @@ public class AuthorizationInterceptorR4Test {
Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output);
ourReturn = Collections.singletonList(output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost);
@ -2146,7 +2436,7 @@ public class AuthorizationInterceptorR4Test {
HttpPost httpPost;
HttpResponse status;
ourReturn = Collections.singletonList((Resource) output);
ourReturn = Collections.singletonList(output);
ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
@ -2610,12 +2900,13 @@ public class AuthorizationInterceptorR4Test {
DummyOrganizationResourceProvider orgProv = new DummyOrganizationResourceProvider();
DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider();
DummyCarePlanResourceProvider cpProv = new DummyCarePlanResourceProvider();
DummyDiagnosticReportResourceProvider drProv = new DummyDiagnosticReportResourceProvider();
PlainProvider plainProvider = new PlainProvider();
ServletHandler proxyHandler = new ServletHandler();
ourServlet = new RestfulServer(ourCtx);
ourServlet.setFhirContext(ourCtx);
ourServlet.setResourceProviders(patProvider, obsProv, encProv, cpProv, orgProv);
ourServlet.setResourceProviders(patProvider, obsProv, encProv, cpProv, orgProv, drProv);
ourServlet.setPlainProviders(plainProvider);
ourServlet.setPagingProvider(new FifoMemoryPagingProvider(100));
ServletHolder servletHolder = new ServletHolder(ourServlet);
@ -2640,6 +2931,9 @@ public class AuthorizationInterceptorR4Test {
@Read(version = true)
public CarePlan read(@IdParam IdType theId) {
ourHitMethod = true;
if (ourReturn.isEmpty()) {
throw new ResourceNotFoundException(theId);
}
return (CarePlan) ourReturn.get(0);
}
@ -2688,6 +2982,23 @@ public class AuthorizationInterceptorR4Test {
}
public static class DummyDiagnosticReportResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return DiagnosticReport.class;
}
@Search()
public List<Resource> search(
@OptionalParam(name = "subject") ReferenceParam theSubject,
@OptionalParam(name = "patient") ReferenceParam thePatient
) {
ourHitMethod = true;
return ourReturn;
}
}
@SuppressWarnings("unused")
public static class DummyObservationResourceProvider implements IResourceProvider {
@ -2733,11 +3044,14 @@ public class AuthorizationInterceptorR4Test {
@Read(version = true)
public Observation read(@IdParam IdType theId) {
ourHitMethod = true;
if (ourReturn.isEmpty()) {
throw new ResourceNotFoundException(theId);
}
return (Observation) ourReturn.get(0);
}
@Search()
public List<Resource> search() {
public List<Resource> search(@OptionalParam(name = "subject") ReferenceParam theSubject) {
ourHitMethod = true;
return ourReturn;
}
@ -2764,6 +3078,7 @@ public class AuthorizationInterceptorR4Test {
}
@SuppressWarnings("unused")
public static class DummyPatientResourceProvider implements IResourceProvider {
@Create()
@ -2861,11 +3176,14 @@ public class AuthorizationInterceptorR4Test {
@Read(version = true)
public Patient read(@IdParam IdType theId) {
ourHitMethod = true;
if (ourReturn.isEmpty()) {
throw new ResourceNotFoundException(theId);
}
return (Patient) ourReturn.get(0);
}
@Search()
public List<Resource> search() {
public List<Resource> search(@OptionalParam(name="_id") IdType theIdParam) {
ourHitMethod = true;
return ourReturn;
}

View File

@ -1917,7 +1917,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
bundle.getNamedChildren("entry", entries);
Element match = null;
for (Element we : entries) {
if (we.getChildValue("fullUrl").equals(targetUrl)) {
if (targetUrl.equals(we.getChildValue("fullUrl"))) {
Element r = we.getNamedChild("resource");
if (version.isEmpty()) {
rule(errors, IssueType.FORBIDDEN, -1, -1, path, match==null, "Multiple matches in bundle for reference " + ref);

View File

@ -317,6 +317,15 @@ public class FhirInstanceValidatorR4Test {
}
@Test
public void testValidateBundleWithNoFullUrl() throws IOException {
String encoded = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/r4/r4-caredove-bundle.json"));
ValidationResult output = myVal.validateWithResult(encoded);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(44, errors.size());
}
@Test
public void testBase64Valid() {
Base64BinaryType value = new Base64BinaryType(new byte[] {2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1});

View File

@ -0,0 +1,815 @@
{
"resourceType": "Bundle",
"type": "transaction",
"timestamp": "2018-03-09T15:21:51.2112Z",
"entry": [
{
"resource": {
"resourceType": "ServiceRequest",
"id": 1,
"text": {
"status": "generated",
"div": "Human readable HTML narrative of entire ServiceRequest and related resources goes here. For brevity sake, it is omitted here, but can be included in an actual transmission"
},
"status": "active",
"intent": "proposal",
"priority": "routine",
"subject": {
"reference": "Patient/1"
},
"authoredOn": "2018-03-09T15:21:51Z",
"requester": {
"reference": "PractitionerRole/1"
},
"performer": {
"reference": "https://www.caredove.com/FHIR3/HealthcareService/8654"
},
"reasonCode": [
{
"text": "Reason for referral narrative goes here"
}
],
"supportingInfo": [
{
"reference": "DocumentReference/1"
}
],
"note": [
{
"text": "Allergies: Penicillin \nSocial History: History of family conflict \nlow social interaction \nFood Allergies: Peanuts"
}
]
},
"request": {
"method": "POST",
"url": "ServiceRequest"
}
},
{
"resource": {
"resourceType": "Patient",
"id": 1,
"identifier": [
{
"type": {
"coding": [
{
"code": "JHN",
"system": "http://hl7.org/fhir/v2/0203"
}
],
"text": "Ontario PHN"
},
"value": "4455 044 033",
"system": "http://ehealthontario.ca/API/FHIR/NamingSystem/ca-on-patient-hcn",
"extension": [
{
"url": "https://www.caredove.com/FHIR3/StructureDefinition/caredove-healthcardversion",
"valueString": "H"
}
]
}
],
"name": [
{
"given": [
"John",
"Scott"
],
"family": "Smith"
}
],
"telecom": [
{
"system": "phone",
"value": "(555) 111-1111",
"use": "mobile",
"rank": 1
},
{
"system": "phone",
"value": "(555) 222-2222",
"rank": 2
},
{
"system": "email",
"value": "testpatient@caredove.com"
}
],
"gender": "male",
"birthDate": "1928-06-29",
"address": [
{
"use": "home",
"type": "physical",
"line": [
"Unit 2",
"50 Albert St."
],
"city": "Waterloo",
"state": "ON",
"postalCode": "K8N 1N1",
"country": "Can"
}
],
"maritalStatus": {
"coding": [
{
"code": "M",
"display": "Married"
}
],
"text": "Married"
},
"contact": [
{
"relationship": [
{
"text": "Alternate Contact"
}
],
"name": {
"given": [
"Shemergency",
"Scott"
],
"family": "McContact"
},
"telecom": [
{
"system": "phone",
"value": "(555) 111-1111",
"use": "mobile",
"rank": 1
},
{
"system": "phone",
"value": "(555) 222-2222",
"rank": 2
},
{
"system": "email",
"value": "testcontact@caredove.com"
}
],
"address": {
"use": "home",
"type": "physical",
"line": [
"Unit 2",
"50 Albert St."
],
"city": "Waterloo",
"state": "ON",
"postalCode": "32819",
"country": "Can"
},
"gender": "female"
}
],
"communication": [
{
"language": {
"coding": [
{
"code": "en",
"display": "English"
}
],
"text": "English"
},
"preferred": true
}
],
"generalPractitioner": [
{
"reference": "PractitionerRole/2"
}
]
},
"request": {
"method": "POST",
"url": "Patient"
}
},
{
"resource": {
"resourceType": "PractitionerRole",
"id": 1,
"practitioner": {
"reference": "Practitioner/1"
},
"organization": {
"reference": "Organization/1"
},
"location": [
{
"reference": "Location/1"
}
],
"telecom": [
{
"system": "phone",
"value": "(555) 111-1111",
"use": "work"
},
{
"system": "email",
"value": "testsender@caredove.com",
"use": "work"
}
]
},
"request": {
"method": "POST",
"url": "PractitionerRole"
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": 1,
"name": [
{
"given": [
"Requesty"
],
"family": "McSenderson"
}
]
},
"request": {
"method": "POST",
"url": "Practitioner"
}
},
{
"resource": {
"resourceType": "Organization",
"id": 1,
"name": "North Sender Clinic"
},
"request": {
"method": "POST",
"url": "Organization"
}
},
{
"resource": {
"resourceType": "Location",
"id": 1,
"name": "Downtown Sender Hub",
"address": {
"use": "work",
"type": "physical",
"line": [
"Suite 11",
"11 King st. West"
],
"city": "Kitchener",
"state": "ON",
"postalCode": "N2L 1T1",
"country": "Can"
}
},
"request": {
"method": "POST",
"url": "Location"
}
},
{
"resource": {
"resourceType": "PractitionerRole",
"id": 2,
"practitioner": {
"reference": "Practitioner/2"
},
"organization": {
"reference": "Organization/2"
},
"location": [
{
"reference": "Location/2"
}
],
"telecom": [
{
"system": "phone",
"value": "(555) 222-2222",
"use": "work"
},
{
"system": "email",
"value": "familydoc@caredove.com",
"use": "work"
}
]
},
"request": {
"method": "POST",
"url": "PractitionerRole"
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": 2,
"name": [
{
"given": [
"Dr. Prim"
],
"family": "Caredoc"
}
]
},
"request": {
"method": "POST",
"url": "Practitioner"
}
},
{
"resource": {
"resourceType": "Organization",
"id": 2,
"name": "Star Family Health Team"
},
"request": {
"method": "POST",
"url": "Organization"
}
},
{
"resource": {
"resourceType": "DocumentReference",
"id": 1,
"status": "current",
"created": "2018-03-09T15:21:51.2112Z",
"description": "Filename or Document Title goes here",
"content": [
{
"attachment": "NEEDS WORK - ATTACHMENT DATA TYPE",
"format": "NEEDS WORK - FORMAT INFO"
}
]
},
"request": {
"method": "POST",
"url": "Practitioner"
}
},
{
"resource": {
"resourceType": "Location",
"id": 2,
"name": "West Side GP Office",
"address": {
"use": "work",
"type": "physical",
"line": [
"22 Weber st. East"
],
"city": "Kitchener",
"state": "ON",
"postalCode": "N2L 2T2",
"country": "Can"
}
},
"request": {
"method": "POST",
"url": "Location"
}
},
{
"resource": {
"resourceType": "Task",
"id": 1,
"basedOn" : {
"reference" : "ServiceRequest/1"
},
"status" : "requested",
"businessStatus " : {
"text" : "Waiting for preliminary review"
},
"intent" : "proposal",
"code" : {
"text" : "Process Request"
},
"description" : "Process and close this referral request",
"authoredOn" : "2018-03-09T15:21:51Z",
"lastModified" : "2018-03-09T15:21:51Z"
},
"request": {
"method": "POST",
"url": "Task"
}
}
]
}

View File

@ -42,6 +42,30 @@
Spring-data (used by the JPA server) has been upgraded to version 2.0.7
(from version 1.11.6). Thanks to Roman Doboni for the pull request!
</action>
<action type="fix">
A crash in the validator was fixed: Validating a Bundle that did not have fullUrl entries
in a required spot caused a NullPointerException.
</action>
<action type="add">
AuthorizationInterceptor now examines requests more closely in order
to block requests early that are not possibly going to return
allowable results when compartment rules are used. For example,
if an AuthorizationInterceptor is configured to allow only
<![CDATA[<b>read</b>]]>
access to compartment
<![CDATA[<code>Patient/123</code>]]>,
a search for
<![CDATA[<code>Observation?subject=987</code>]]>
will now be blocked before the method handler is called. Previously
the search was performed and the results were examined in order to
determine whether they were all in the appropriate compartment, but
this incurs a performance cost, and means that this search would
successfully return an empty Bundle if no matches were present.
<![CDATA[<br/><br/>]]>
A new setting on AuthorizationInterceptor called
<![CDATA[<code>setFlags(flags)</code>]]>
can be used to maintain the previous behaviour.
</action>
</release>
<release version="3.4.0" date="2018-05-28">
<action type="add">