diff --git a/api/src/main/java/io/smallrye/context/spi/WrappingThreadContextSnapshot.java b/api/src/main/java/io/smallrye/context/spi/WrappingThreadContextSnapshot.java
new file mode 100644
index 00000000..2ed265f7
--- /dev/null
+++ b/api/src/main/java/io/smallrye/context/spi/WrappingThreadContextSnapshot.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * 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.
+ */
+package io.smallrye.context.spi;
+
+import java.util.concurrent.Callable;
+
+import org.eclipse.microprofile.context.spi.ThreadContextSnapshot;
+
+/**
+ * An extension of {code ThreadContextSnapshot} which enables the snapshot to
+ * perform propagation by wrapping the task.
+ *
+ * @author Darran Lofthouse
+ */
+@FunctionalInterface
+public interface WrappingThreadContextSnapshot extends ThreadContextSnapshot {
+
+ /**
+ * Does this snapshot need to wrap the underlying task instead of directly
+ * manipulating ThreadLocals.
+ *
+ * @return {@code true} if this snapshot needs to wrap the underlying task.
+ */
+ default boolean needsToWrap() {
+ return false;
+ }
+
+ /**
+ * Wrap the provided task to ensure the context being propagated is correctly
+ * established and cleared as the underlying task is called.
+ *
+ * @param task the task to wrap.
+ * @return the wrapped task.
+ */
+ default Callable wrap(Callable callable) {
+ return callable;
+ }
+
+}
diff --git a/core/src/main/java/io/smallrye/context/CleanAutoCloseable.java b/core/src/main/java/io/smallrye/context/CleanAutoCloseable.java
index ca7c0d48..6df2e712 100644
--- a/core/src/main/java/io/smallrye/context/CleanAutoCloseable.java
+++ b/core/src/main/java/io/smallrye/context/CleanAutoCloseable.java
@@ -3,8 +3,10 @@
/**
* AutoCloseable interface which doesn't throw.
*/
-@FunctionalInterface
-public interface CleanAutoCloseable extends AutoCloseable {
+public interface CleanAutoCloseable extends AutoCloseable {
+
+ T callNoChecked();
+
/**
* Close this resource, no exception thrown.
*/
diff --git a/core/src/main/java/io/smallrye/context/SmallRyeThreadContext.java b/core/src/main/java/io/smallrye/context/SmallRyeThreadContext.java
index e59897ba..64c7aea6 100644
--- a/core/src/main/java/io/smallrye/context/SmallRyeThreadContext.java
+++ b/core/src/main/java/io/smallrye/context/SmallRyeThreadContext.java
@@ -68,6 +68,12 @@ public class SmallRyeThreadContext implements ThreadContext {
.threadLocal(SmallRyeThreadContextStorageDeclaration.class);
private final static CleanAutoCloseable NULL_THREAD_STATE = new CleanAutoCloseable() {
+
+ @Override
+ public Object callNoChecked() {
+ throw new IllegalStateException();
+ }
+
@Override
public void close() {
currentThreadContext.remove();
@@ -84,7 +90,7 @@ public void execute(Runnable command) {
/**
* Updates the current @{link SmallRyeThreadContext} in use by the current thread, and returns an
* object suitable for use in try-with-resource to restore the previous value.
- *
+ *
* @param threadContext the @{link SmallRyeThreadContext} to use
* @return an object suitable for use in try-with-resource to restore the previous value.
*/
@@ -96,6 +102,12 @@ public static CleanAutoCloseable withThreadContext(SmallRyeThreadContext threadC
return NULL_THREAD_STATE;
} else {
return new CleanAutoCloseable() {
+
+ @Override
+ public Object callNoChecked() {
+ throw new IllegalStateException();
+ }
+
@Override
public void close() {
currentThreadContext.set(oldValue);
@@ -108,7 +120,7 @@ public void close() {
/**
* Invokes the given @{link Runnable} with the current @{link SmallRyeThreadContext} updated to the given value
* for the current thread.
- *
+ *
* @param threadContext the @{link SmallRyeThreadContext} to use
* @param f the @{link Runnable} to invoke
*/
@@ -129,7 +141,7 @@ public static void withThreadContext(SmallRyeThreadContext threadContext, Runnab
/**
* Returns the current thread's @{link SmallRyeThreadContext} if set, or a @{link SmallRyeThreadContext}
* which propagates all contexts.
- *
+ *
* @return the current thread's @{link SmallRyeThreadContext} if set, or a @{link SmallRyeThreadContext}
* which propagates all contexts.
*/
@@ -140,7 +152,7 @@ public static SmallRyeThreadContext getCurrentThreadContextOrPropagatedContexts(
/**
* Returns the current thread's @{link SmallRyeThreadContext} if set, or a @{link SmallRyeThreadContext}
* which clears all contexts.
- *
+ *
* @return the current thread's @{link SmallRyeThreadContext} if set, or a @{link SmallRyeThreadContext}
* which clears all contexts.
*/
@@ -151,7 +163,7 @@ public static SmallRyeThreadContext getCurrentThreadContextOrClearedContexts() {
/**
* Returns the current thread's @{link SmallRyeThreadContext} if set, or the given @{link SmallRyeThreadContext}
* default value.
- *
+ *
* @param defaultValue the default value to use
* @return the current thread's @{link SmallRyeThreadContext} if set, or the given @{link SmallRyeThreadContext}
* default value.
@@ -163,7 +175,7 @@ public static SmallRyeThreadContext getCurrentThreadContext(SmallRyeThreadContex
/**
* Returns the current thread's @{link SmallRyeThreadContext} if set, or null.
- *
+ *
* @return the current thread's @{link SmallRyeThreadContext} if set, or null.
*/
public static SmallRyeThreadContext getCurrentThreadContext() {
@@ -200,7 +212,7 @@ public ExecutorService getDefaultExecutor() {
/**
* Returns true if this thread context has no context to propagate nor clear, and so
* will not contextualise anything.
- *
+ *
* @return true if this thread context has no context to propagate nor clear
*/
public boolean isEmpty() {
@@ -209,7 +221,7 @@ public boolean isEmpty() {
/**
* Returns true if the given lambda instance is already contextualized
- *
+ *
* @param lambda the lambda to test
* @return true if the given lambda instance is already contextualized
*/
@@ -236,7 +248,7 @@ public static Builder builder() {
* or the default executor service as set by
* {@link SmallRyeContextManager.Builder#withDefaultExecutorService(ExecutorService)},
* or otherwise have no default executor.
- *
+ *
* If this thread context has no default executor, the new stage and all dependent stages created from it, and so forth,
* have no default asynchronous execution facility and must raise {@link java.lang.UnsupportedOperationException}
* for all *Async
methods that do not specify an executor. For example,
@@ -284,7 +296,7 @@ public CompletableFuture withContextCapture(CompletableFuture future,
* or the default executor service as set by
* {@link SmallRyeContextManager.Builder#withDefaultExecutorService(ExecutorService)},
* or otherwise have no default executor.
- *
+ *
* If this thread context has no default executor, the new stage and all dependent stages created from it, and so forth,
* and/or cleared as described in the documentation of the {@link ManagedExecutor} class, except that
* this ThreadContext instance takes the place of the default asynchronous execution facility in
diff --git a/core/src/main/java/io/smallrye/context/impl/CapturedContextState.java b/core/src/main/java/io/smallrye/context/impl/CapturedContextState.java
index 0df1faad..c5889a5e 100644
--- a/core/src/main/java/io/smallrye/context/impl/CapturedContextState.java
+++ b/core/src/main/java/io/smallrye/context/impl/CapturedContextState.java
@@ -1,8 +1,10 @@
package io.smallrye.context.impl;
+import java.util.concurrent.Callable;
+
import io.smallrye.context.CleanAutoCloseable;
@FunctionalInterface
public interface CapturedContextState {
- CleanAutoCloseable begin();
+ CleanAutoCloseable begin(Callable callable);
}
diff --git a/core/src/main/java/io/smallrye/context/impl/SlowActiveContextState.java b/core/src/main/java/io/smallrye/context/impl/SlowActiveContextState.java
index 3b2c9ab4..0fde879c 100644
--- a/core/src/main/java/io/smallrye/context/impl/SlowActiveContextState.java
+++ b/core/src/main/java/io/smallrye/context/impl/SlowActiveContextState.java
@@ -1,36 +1,55 @@
package io.smallrye.context.impl;
import java.util.List;
+import java.util.concurrent.Callable;
import org.eclipse.microprofile.context.spi.ThreadContextController;
import org.eclipse.microprofile.context.spi.ThreadContextSnapshot;
import io.smallrye.context.CleanAutoCloseable;
import io.smallrye.context.SmallRyeThreadContext;
+import io.smallrye.context.spi.WrappingThreadContextSnapshot;
/**
* Restores a context and allows you to clean it up (unrestore it).
*/
-public class SlowActiveContextState implements CleanAutoCloseable {
+public class SlowActiveContextState implements CleanAutoCloseable {
private final ThreadContextController[] activeContext;
private final CleanAutoCloseable activeThreadContext;
+ private final Callable callable;
/**
* Restores a previously captured context.
- *
+ *
* @param threadContext the thread context
* @param threadContextSnapshots the captured snapshots
*/
- public SlowActiveContextState(SmallRyeThreadContext threadContext, List threadContextSnapshots) {
+ public SlowActiveContextState(SmallRyeThreadContext threadContext, List threadContextSnapshots,
+ Callable callable) {
activeContext = new ThreadContextController[threadContextSnapshots.size()];
int i = 0;
for (ThreadContextSnapshot threadContextSnapshot : threadContextSnapshots) {
activeContext[i++] = threadContextSnapshot.begin();
+ if (threadContextSnapshot instanceof WrappingThreadContextSnapshot
+ && ((WrappingThreadContextSnapshot) threadContextSnapshot).needsToWrap()) {
+ callable = ((WrappingThreadContextSnapshot) threadContextSnapshot).wrap(callable);
+ }
}
+ this.callable = callable;
activeThreadContext = SmallRyeThreadContext.withThreadContext(threadContext);
}
+ @Override
+ public T callNoChecked() {
+ try {
+ return callable.call();
+ } catch (Exception e) {
+ Util.rethrow(e);
+ return null;
+ }
+ }
+
/**
* Unrestores / clean-up a previously restored context.
*/
diff --git a/core/src/main/java/io/smallrye/context/impl/SlowCapturedContextState.java b/core/src/main/java/io/smallrye/context/impl/SlowCapturedContextState.java
index 676bdf80..970395d5 100644
--- a/core/src/main/java/io/smallrye/context/impl/SlowCapturedContextState.java
+++ b/core/src/main/java/io/smallrye/context/impl/SlowCapturedContextState.java
@@ -1,6 +1,7 @@
package io.smallrye.context.impl;
import java.util.List;
+import java.util.concurrent.Callable;
import org.eclipse.microprofile.context.spi.ThreadContextSnapshot;
@@ -17,7 +18,7 @@ public class SlowCapturedContextState implements CapturedContextState {
/**
* Captures the current context according to the given ThreadContext
- *
+ *
* @param threadContext the thread context
*/
public SlowCapturedContextState(SmallRyeThreadContext threadContext) {
@@ -27,10 +28,10 @@ public SlowCapturedContextState(SmallRyeThreadContext threadContext) {
/**
* Restores the captured context and returns an instance that can unrestore (cleanup) it.
- *
+ *
* @return the captured context state
*/
- public SlowActiveContextState begin() {
- return new SlowActiveContextState(threadContext, threadContextSnapshots);
+ public SlowActiveContextState begin(Callable callable) {
+ return new SlowActiveContextState(threadContext, threadContextSnapshots, callable);
}
}
diff --git a/core/src/main/java/io/smallrye/context/impl/Util.java b/core/src/main/java/io/smallrye/context/impl/Util.java
new file mode 100644
index 00000000..938e9ee7
--- /dev/null
+++ b/core/src/main/java/io/smallrye/context/impl/Util.java
@@ -0,0 +1,10 @@
+package io.smallrye.context.impl;
+
+/**
+ * @author Kabir Khan
+ */
+public class Util {
+ public static void rethrow(Throwable t) throws T {
+ throw (T) t;
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualBiConsumer.java b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualBiConsumer.java
index 5e6fbbef..1e0ba9dd 100644
--- a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualBiConsumer.java
+++ b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualBiConsumer.java
@@ -17,8 +17,11 @@ public SlowContextualBiConsumer(CapturedContextState state, BiConsumer con
@Override
public void accept(T t, U u) {
- try (CleanAutoCloseable activeState = state.begin()) {
+ try (CleanAutoCloseable activeState = state.begin(() -> {
consumer.accept(t, u);
+ return null;
+ })) {
+ activeState.callNoChecked();
}
}
}
\ No newline at end of file
diff --git a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualBiFunction.java b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualBiFunction.java
index ad6c04c1..7d76df48 100644
--- a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualBiFunction.java
+++ b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualBiFunction.java
@@ -17,8 +17,8 @@ public SlowContextualBiFunction(CapturedContextState state, BiFunction
@Override
public R apply(T t, U u) {
- try (CleanAutoCloseable activeState = state.begin()) {
- return function.apply(t, u);
+ try (CleanAutoCloseable activeState = state.begin(() -> function.apply(t, u))) {
+ return activeState.callNoChecked();
}
}
}
\ No newline at end of file
diff --git a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualCallable.java b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualCallable.java
index 398144ed..23989b6e 100644
--- a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualCallable.java
+++ b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualCallable.java
@@ -17,8 +17,8 @@ public SlowContextualCallable(CapturedContextState state, Callable callable)
@Override
public R call() throws Exception {
- try (CleanAutoCloseable activeState = state.begin()) {
- return callable.call();
+ try (CleanAutoCloseable activeState = state.begin(callable)) {
+ return activeState.callNoChecked();
}
}
}
\ No newline at end of file
diff --git a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualConsumer.java b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualConsumer.java
index 63200f43..e5731681 100644
--- a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualConsumer.java
+++ b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualConsumer.java
@@ -17,8 +17,11 @@ public SlowContextualConsumer(CapturedContextState state, Consumer consumer)
@Override
public void accept(T t) {
- try (CleanAutoCloseable activeState = state.begin()) {
+ try (CleanAutoCloseable activeState = state.begin(() -> {
consumer.accept(t);
+ return null;
+ })) {
+ activeState.callNoChecked();
}
}
}
\ No newline at end of file
diff --git a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualExecutor.java b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualExecutor.java
index 897e596a..3285504f 100644
--- a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualExecutor.java
+++ b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualExecutor.java
@@ -15,8 +15,11 @@ public SlowContextualExecutor(CapturedContextState state) {
@Override
public void execute(Runnable command) {
- try (CleanAutoCloseable foo = state.begin()) {
+ try (CleanAutoCloseable foo = state.begin(() -> {
command.run();
+ return null;
+ })) {
+ foo.callNoChecked();
}
}
diff --git a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualFunction.java b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualFunction.java
index 7329e519..8f7b78eb 100644
--- a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualFunction.java
+++ b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualFunction.java
@@ -17,8 +17,8 @@ public SlowContextualFunction(CapturedContextState state, Function functio
@Override
public R apply(T t) {
- try (CleanAutoCloseable activeState = state.begin()) {
- return function.apply(t);
+ try (CleanAutoCloseable activeState = state.begin(() -> function.apply(t))) {
+ return activeState.callNoChecked();
}
}
}
\ No newline at end of file
diff --git a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualRunnable.java b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualRunnable.java
index 9c7ee6b5..34225755 100644
--- a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualRunnable.java
+++ b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualRunnable.java
@@ -15,8 +15,11 @@ public SlowContextualRunnable(CapturedContextState state, Runnable runnable) {
@Override
public void run() {
- try (CleanAutoCloseable activeState = state.begin()) {
+ try (CleanAutoCloseable activeState = state.begin(() -> {
runnable.run();
+ return null;
+ })) {
+ activeState.callNoChecked();
}
}
}
diff --git a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualSupplier.java b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualSupplier.java
index 84f27ee2..5d5359ab 100644
--- a/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualSupplier.java
+++ b/core/src/main/java/io/smallrye/context/impl/wrappers/SlowContextualSupplier.java
@@ -17,8 +17,8 @@ public SlowContextualSupplier(CapturedContextState state, Supplier supplier)
@Override
public R get() {
- try (CleanAutoCloseable activeState = state.begin()) {
- return supplier.get();
+ try (CleanAutoCloseable activeState = state.begin(supplier::get)) {
+ return activeState.callNoChecked();
}
}
}
diff --git a/pom.xml b/pom.xml
index ec5e0a0e..44b22628 100644
--- a/pom.xml
+++ b/pom.xml
@@ -105,6 +105,7 @@
application
propagators-rxjava1
propagators-rxjava2
+ security
tests
tck
api
diff --git a/security/pom.xml b/security/pom.xml
new file mode 100644
index 00000000..e4d3dc64
--- /dev/null
+++ b/security/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+ io.smallrye
+ smallrye-context-propagation-parent
+ 1.1.1-SNAPSHOT
+
+ smallrye-context-propagation-security
+ smallrye-context-propagation-security
+
+
+
+ ${project.groupId}
+ smallrye-context-propagation-api
+ ${project.version}
+
+
+ junit
+ junit
+ test
+
+
+ org.eclipse.microprofile.context-propagation
+ microprofile-context-propagation-api
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ test-jar
+
+
+
+
+
+
+
+
+
+ coverage
+
+ @{jacocoArgLine}
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+
+
+
+
diff --git a/security/src/main/java/io/smallrye/context/security/context/propagation/SecurityContextProvider.java b/security/src/main/java/io/smallrye/context/security/context/propagation/SecurityContextProvider.java
new file mode 100644
index 00000000..ec96e47c
--- /dev/null
+++ b/security/src/main/java/io/smallrye/context/security/context/propagation/SecurityContextProvider.java
@@ -0,0 +1,84 @@
+package io.smallrye.context.security.context.propagation;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import org.eclipse.microprofile.context.ThreadContext;
+import org.eclipse.microprofile.context.spi.ThreadContextController;
+import org.eclipse.microprofile.context.spi.ThreadContextProvider;
+import org.eclipse.microprofile.context.spi.ThreadContextSnapshot;
+
+import io.smallrye.context.spi.WrappingThreadContextSnapshot;
+
+public class SecurityContextProvider implements ThreadContextProvider {
+
+ private static final ThreadContextController NOOP_THREAD_CONTEXT_CONTROLLER = new ThreadContextController() {
+ @Override
+ public void endContext() throws IllegalStateException {
+ }
+ };
+
+ @Override
+ public ThreadContextSnapshot currentContext(Map props) {
+ return new SecurityThreadContextSnapshot(true);
+ }
+
+ @Override
+ public ThreadContextSnapshot clearedContext(Map props) {
+ return new SecurityThreadContextSnapshot(false);
+ }
+
+ @Override
+ public String getThreadContextType() {
+ return ThreadContext.SECURITY;
+ }
+
+ static final class SecurityThreadContextSnapshot implements WrappingThreadContextSnapshot {
+
+ private final AccessControlContext accessControlContext;
+
+ SecurityThreadContextSnapshot(final boolean capture) {
+ if (capture) {
+ accessControlContext = AccessController.getContext();
+ } else {
+ accessControlContext = null;
+ }
+ }
+
+ @Override
+ public ThreadContextController begin() {
+ return NOOP_THREAD_CONTEXT_CONTROLLER;
+ }
+
+ @Override
+ public boolean needsToWrap() {
+ return accessControlContext != null;
+ }
+
+ @Override
+ public Callable wrap(Callable callable) {
+ if (accessControlContext == null) {
+ return callable;
+ }
+
+ return new Callable() {
+
+ @Override
+ public T call() throws Exception {
+ try {
+ return AccessController.doPrivilegedWithCombiner((PrivilegedExceptionAction) callable::call,
+ accessControlContext);
+ } catch (PrivilegedActionException e) {
+ throw e.getException();
+ }
+ }
+ };
+ }
+
+ }
+
+}
diff --git a/security/src/main/resources/META-INF/services/org.eclipse.microprofile.context.spi.ThreadContextProvider b/security/src/main/resources/META-INF/services/org.eclipse.microprofile.context.spi.ThreadContextProvider
new file mode 100644
index 00000000..c250a3d5
--- /dev/null
+++ b/security/src/main/resources/META-INF/services/org.eclipse.microprofile.context.spi.ThreadContextProvider
@@ -0,0 +1 @@
+io.smallrye.context.security.context.propagation.SecurityContextProvider
\ No newline at end of file
diff --git a/tests/pom.xml b/tests/pom.xml
index ac0bdec6..e5788499 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -78,6 +78,12 @@
tests
test-jar
+
+ ${project.groupId}
+ smallrye-context-propagation-security
+ ${project.version}
+ test
+
org.jboss.weld.se
weld-se-shaded
diff --git a/tests/src/test/java/io/smallrye/context/test/CompletableFutureTest.java b/tests/src/test/java/io/smallrye/context/test/CompletableFutureTest.java
index aaf6531d..faea13a1 100644
--- a/tests/src/test/java/io/smallrye/context/test/CompletableFutureTest.java
+++ b/tests/src/test/java/io/smallrye/context/test/CompletableFutureTest.java
@@ -297,7 +297,7 @@ public void testExistingCFWrapping() throws InterruptedException, ExecutionExcep
SmallRyeThreadContext threadContext = managedExecutor.getThreadContext();
Assert.assertNotNull(threadContext);
ThreadContextProviderPlan plan = threadContext.getPlan();
- Assert.assertEquals(4, plan.clearedProviders.size());
+ Assert.assertEquals(5, plan.clearedProviders.size());
Assert.assertTrue(plan.unchangedProviders.isEmpty());
Assert.assertEquals(1, plan.propagatedProviders.size());
diff --git a/tests/src/test/java/io/smallrye/context/test/SecurityTest.java b/tests/src/test/java/io/smallrye/context/test/SecurityTest.java
new file mode 100644
index 00000000..6f823a0f
--- /dev/null
+++ b/tests/src/test/java/io/smallrye/context/test/SecurityTest.java
@@ -0,0 +1,122 @@
+package io.smallrye.context.test;
+
+import static org.junit.Assert.assertSame;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.microprofile.context.ManagedExecutor;
+import org.eclipse.microprofile.context.ThreadContext;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import io.smallrye.context.SmallRyeManagedExecutor;
+
+/**
+ * The most basic level of security propagation could be the propagation of both a JAAS Subject,
+ * and AccessControlContext - before vendor specific representations are added these are the
+ * original approaches available.
+ *
+ * This test adds tests to verify a Subject can be successfully propagated.
+ */
+public class SecurityTest {
+
+ private static Subject identity;
+ private ExecutorService executorService;
+
+ @BeforeClass
+ public static void setupSubject() {
+ Subject identity = new Subject();
+ identity.setReadOnly();
+
+ // We don't need content as we can verify the instance was propagated.
+ SecurityTest.identity = identity;
+ }
+
+ @AfterClass
+ public static void clearSubject() {
+ identity = null;
+ }
+
+ @Before
+ public void createExecutor() throws Exception {
+ executorService = Executors.newSingleThreadExecutor();
+ // Ensure we initialise the initial Thread so we don't accidentally
+ // capture the AccessControlContext.
+ Future> result = executorService.submit(() -> {
+ });
+ result.get();
+ }
+
+ @After
+ public void shutDownExecutor() {
+ executorService.shutdown();
+ executorService = null;
+ }
+
+ @Test
+ public void testManagedExecutor() {
+ Subject.doAs(identity, (PrivilegedAction) () -> {
+ _testManagedExecutor();
+ return null;
+ });
+ }
+
+ private void _testManagedExecutor() {
+ assertCorrectSubject();
+
+ ManagedExecutor executor = SmallRyeManagedExecutor.builder()
+ .withExecutorService(executorService)
+ .propagated(ThreadContext.SECURITY)
+ .build();
+
+ CompletableFuture future = executor.runAsync(this::assertCorrectSubject);
+ future.join();
+
+ executor.shutdown();
+ }
+
+ private void assertCorrectSubject() {
+ AccessControlContext accessControllContext = AccessController.getContext();
+ Subject currentSubject = Subject.getSubject(accessControllContext);
+
+ assertSame("Same Subject", identity, currentSubject);
+ }
+
+ @Test
+ public void testThreadContext() throws Exception {
+ Subject.doAs(identity, (PrivilegedExceptionAction) () -> {
+ _testThreadContext();
+ return null;
+ });
+ }
+
+ private void _testThreadContext() throws InterruptedException, ExecutionException {
+ assertCorrectSubject();
+
+ ThreadContext threadContext = ThreadContext.builder()
+ .propagated(ThreadContext.SECURITY)
+ .build();
+
+ Runnable runnable = threadContext.contextualRunnable(this::assertCorrectSubject);
+
+ Future> future = executorService.submit(runnable);
+ future.get();
+ }
+
+ // TODO Opposite Tests, i.e. Threads with an AccessControlContext and Subject should be
+ // cleared for execution.
+
+}