AuthorizationInterceptor concurrency failure (#3528)

* Fix #3256 - AuthorizationInterceptor concurrency failure

* Test fixes
This commit is contained in:
James Agnew 2022-04-15 15:27:57 -04:00 committed by GitHub
parent c29bb462c5
commit 7dec2e334d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 81 additions and 2 deletions

View File

@ -217,6 +217,12 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
} }
} }
} }
// Make the map of lists completely unmodifiable
for (String nextKey : new ArrayList<>(compartmentNameToSearchParams.keySet())) {
List<RuntimeSearchParam> nextList = compartmentNameToSearchParams.get(nextKey);
compartmentNameToSearchParams.put(nextKey, Collections.unmodifiableList(nextList));
}
myCompartmentNameToSearchParams = Collections.unmodifiableMap(compartmentNameToSearchParams); myCompartmentNameToSearchParams = Collections.unmodifiableMap(compartmentNameToSearchParams);
Class<?> target = getImplementingClass(); Class<?> target = getImplementingClass();

View File

@ -748,7 +748,7 @@ public class FhirTerser {
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName); List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
//If passed an additional set of searchparameter names, add them for comparison purposes. // If passed an additional set of searchparameter names, add them for comparison purposes.
if (theAdditionalCompartmentParamNames != null) { if (theAdditionalCompartmentParamNames != null) {
List<RuntimeSearchParam> additionalParams = theAdditionalCompartmentParamNames.stream().map(sourceDef::getSearchParam) List<RuntimeSearchParam> additionalParams = theAdditionalCompartmentParamNames.stream().map(sourceDef::getSearchParam)
.filter(Objects::nonNull) .filter(Objects::nonNull)
@ -756,6 +756,9 @@ public class FhirTerser {
if (params == null || params.isEmpty()) { if (params == null || params.isEmpty()) {
params = additionalParams; params = additionalParams;
} else { } else {
List<RuntimeSearchParam> existingParams = params;
params = new ArrayList<>(existingParams.size() + additionalParams.size());
params.addAll(existingParams);
params.addAll(additionalParams); params.addAll(additionalParams);
} }
} }

View File

@ -62,6 +62,10 @@ public class UrlUtil {
private static final String URL_FORM_PARAMETER_OTHER_SAFE_CHARS = "-_.*"; private static final String URL_FORM_PARAMETER_OTHER_SAFE_CHARS = "-_.*";
private static final Escaper PARAMETER_ESCAPER = new PercentEscaper(URL_FORM_PARAMETER_OTHER_SAFE_CHARS, false); private static final Escaper PARAMETER_ESCAPER = new PercentEscaper(URL_FORM_PARAMETER_OTHER_SAFE_CHARS, false);
public static String sanitizeBaseUrl(String theBaseUrl) {
return theBaseUrl.replaceAll("[^a-zA-Z0-9:/._-]", "");
}
public static class UrlParts { public static class UrlParts {
private String myParams; private String myParams;
private String myResourceId; private String myResourceId;

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 3256
title: "An occasional concurrency failure in AuthorizationInterceptor has been resolved. Thanks to
Martin Visser for reporting and providing a reproducible test case!"

View File

@ -789,7 +789,12 @@ public class ResponseHighlighterInterceptor {
InputStream jsStream = ResponseHighlighterInterceptor.class.getResourceAsStream("ResponseHighlighter.js"); InputStream jsStream = ResponseHighlighterInterceptor.class.getResourceAsStream("ResponseHighlighter.js");
String jsStr = jsStream != null ? IOUtils.toString(jsStream, StandardCharsets.UTF_8) : "console.log('ResponseHighlighterInterceptor: javascript theResource not found')"; String jsStr = jsStream != null ? IOUtils.toString(jsStream, StandardCharsets.UTF_8) : "console.log('ResponseHighlighterInterceptor: javascript theResource not found')";
jsStr = jsStr.replace("FHIR_BASE", theRequestDetails.getServerBaseForRequest());
String baseUrl = theRequestDetails.getServerBaseForRequest();
baseUrl = UrlUtil.sanitizeBaseUrl(baseUrl);
jsStr = jsStr.replace("FHIR_BASE", baseUrl);
outputBuffer.append("<script type=\"text/javascript\">"); outputBuffer.append("<script type=\"text/javascript\">");
outputBuffer.append(jsStr); outputBuffer.append(jsStr);
outputBuffer.append("</script>\n"); outputBuffer.append("</script>\n");

View File

@ -398,6 +398,9 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
if (params == null || params.isEmpty()) { if (params == null || params.isEmpty()) {
params = additionalParams; params = additionalParams;
} else { } else {
List<RuntimeSearchParam> existingParams = params;
params = new ArrayList<>(existingParams.size() + additionalParams.size());
params.addAll(existingParams);
params.addAll(additionalParams); params.addAll(additionalParams);
} }

View File

@ -19,6 +19,7 @@ import org.hl7.fhir.r4.model.DocumentReference;
import org.hl7.fhir.r4.model.Enumeration; import org.hl7.fhir.r4.model.Enumeration;
import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.MarkdownType; import org.hl7.fhir.r4.model.MarkdownType;
import org.hl7.fhir.r4.model.Medication; import org.hl7.fhir.r4.model.Medication;
@ -51,6 +52,10 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -1427,6 +1432,40 @@ public class FhirTerserR4Test {
assertEquals("cid:device@bundle", elems.get(1).getReferenceElement().getValue()); assertEquals("cid:device@bundle", elems.get(1).getReferenceElement().getValue());
} }
@Test
public void testConcurrentTerserCalls() throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
try {
FhirContext ctx = FhirContext.forR4();
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Runnable runnable = () -> {
Observation observation = new Observation();
observation.setSubject(new Reference("Patient/123"));
HashSet<String> additionalCompartmentParamNames = new HashSet<>();
additionalCompartmentParamNames.add("test");
boolean outcome = ctx.newTerser().isSourceInCompartmentForTarget("Patient", observation, new IdType("Patient/123"), additionalCompartmentParamNames);
assertTrue(outcome);
};
Future<?> future = executor.submit(runnable);
futures.add(future);
}
for (var next : futures) {
next.get();
}
}finally {
executor.shutdown();
}
}
private List<String> toStrings(List<StringType> theStrings) { private List<String> toStrings(List<StringType> theStrings) {
ArrayList<String> retVal = new ArrayList<>(); ArrayList<String> retVal = new ArrayList<>();

14
pom.xml
View File

@ -1713,6 +1713,20 @@
<groupId>org.quartz-scheduler</groupId> <groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId> <artifactId>quartz</artifactId>
<version>2.3.2</version> <version>2.3.2</version>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java7</artifactId>
</exclusion>
<exclusion>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
</exclusion>
<exclusion>
<groupId>com.mchange</groupId>
<artifactId>mchange-commons-java</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>