From 247ce5dcab29e3c03132d3f3aad149f9fed0c3d6 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg Date: Mon, 11 Sep 2023 10:44:12 -0500 Subject: [PATCH] Add integration tests for virtual threads Closes gh-12790 --- .../DelegatingSecurityContextTestUtils.java | 102 ++++++++++++ ...curityContextExecutorIntegrationTests.java | 75 +++++++++ ...ontextExecutorServiceIntegrationTests.java | 98 ++++++++++++ ...eduledExecutorServiceIntegrationTests.java | 121 ++++++++++++++ ...chedulingTaskExecutorIntegrationTests.java | 148 ++++++++++++++++++ ...yContextTaskSchedulerIntegrationTests.java | 125 +++++++++++++++ ...textAsyncTaskExecutorIntegrationTests.java | 143 +++++++++++++++++ ...tyContextTaskExecutorIntegrationTests.java | 75 +++++++++ 8 files changed, 887 insertions(+) create mode 100644 core/src/test/java/org/springframework/security/DelegatingSecurityContextTestUtils.java create mode 100644 core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextExecutorIntegrationTests.java create mode 100644 core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextExecutorServiceIntegrationTests.java create mode 100644 core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextScheduledExecutorServiceIntegrationTests.java create mode 100644 core/src/test/java/org/springframework/security/scheduling/DelegatingSecurityContextSchedulingTaskExecutorIntegrationTests.java create mode 100644 core/src/test/java/org/springframework/security/scheduling/DelegatingSecurityContextTaskSchedulerIntegrationTests.java create mode 100644 core/src/test/java/org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutorIntegrationTests.java create mode 100644 core/src/test/java/org/springframework/security/task/DelegatingSecurityContextTaskExecutorIntegrationTests.java diff --git a/core/src/test/java/org/springframework/security/DelegatingSecurityContextTestUtils.java b/core/src/test/java/org/springframework/security/DelegatingSecurityContextTestUtils.java new file mode 100644 index 0000000000..0c05cf588a --- /dev/null +++ b/core/src/test/java/org/springframework/security/DelegatingSecurityContextTestUtils.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * 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 + * + * https://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 org.springframework.security; + +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.springframework.scheduling.TaskScheduler; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * @author Steve Riesenberg + */ +public final class DelegatingSecurityContextTestUtils { + + private DelegatingSecurityContextTestUtils() { + } + + public static SecurityContext runAndReturn(ThreadFactory threadFactory, + Function factory, BiConsumer fn) throws Exception { + CountDownLatch countDownLatch = new CountDownLatch(1); + AtomicReference result = new AtomicReference<>(); + ScheduledExecutorService delegate = Executors.newSingleThreadScheduledExecutor(threadFactory); + try { + T executor = factory.apply(delegate); + Runnable task = () -> { + result.set(SecurityContextHolder.getContext()); + countDownLatch.countDown(); + }; + fn.accept(executor, task); + countDownLatch.await(); + + return result.get(); + } + finally { + delegate.shutdown(); + } + } + + public static SecurityContext runAndReturn(ThreadFactory threadFactory, + Function factory, BiFunction> fn) + throws Exception { + CountDownLatch countDownLatch = new CountDownLatch(1); + AtomicReference result = new AtomicReference<>(); + ScheduledExecutorService delegate = Executors.newSingleThreadScheduledExecutor(threadFactory); + try { + T taskScheduler = factory.apply(delegate); + Runnable task = () -> { + result.set(SecurityContextHolder.getContext()); + countDownLatch.countDown(); + }; + ScheduledFuture future = fn.apply(taskScheduler, task); + countDownLatch.await(); + future.cancel(false); + + return result.get(); + } + finally { + delegate.shutdown(); + } + } + + public static SecurityContext callAndReturn(ThreadFactory threadFactory, + Function factory, + BiFunction, Future> fn) throws Exception { + ScheduledExecutorService delegate = Executors.newSingleThreadScheduledExecutor(threadFactory); + try { + T executor = factory.apply(delegate); + Callable task = SecurityContextHolder::getContext; + return fn.apply(executor, task).get(); + } + finally { + delegate.shutdown(); + } + } + +} diff --git a/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextExecutorIntegrationTests.java b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextExecutorIntegrationTests.java new file mode 100644 index 0000000000..92098ed79b --- /dev/null +++ b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextExecutorIntegrationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020-2023 the original author or authors. + * + * 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 + * + * https://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 org.springframework.security.concurrent; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.core.task.VirtualThreadTaskExecutor; +import org.springframework.security.DelegatingSecurityContextTestUtils; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Riesenberg + */ +public class DelegatingSecurityContextExecutorIntegrationTests { + + @Test + public void executeWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void executeWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext executeAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn( + threadFactory, + this::createExecutor, + Executor::execute + ); + // @formatter:on + } + + private DelegatingSecurityContextExecutor createExecutor(ScheduledExecutorService delegate) { + return new DelegatingSecurityContextExecutor(delegate, securityContext()); + } + + private static SecurityContext securityContext() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); + + return securityContext; + } + +} diff --git a/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextExecutorServiceIntegrationTests.java b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextExecutorServiceIntegrationTests.java new file mode 100644 index 0000000000..55df678328 --- /dev/null +++ b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextExecutorServiceIntegrationTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2020-2023 the original author or authors. + * + * 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 + * + * https://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 org.springframework.security.concurrent; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.core.task.VirtualThreadTaskExecutor; +import org.springframework.security.DelegatingSecurityContextTestUtils; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Riesenberg + */ +public class DelegatingSecurityContextExecutorServiceIntegrationTests { + + @Test + public void executeWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void executeWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext executeAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn( + threadFactory, + this::createExecutor, + ExecutorService::execute + ); + // @formatter:on + } + + @Test + public void submitWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void submitWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext submitAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.callAndReturn( + threadFactory, + this::createExecutor, + ExecutorService::submit + ); + // @formatter:on + } + + private DelegatingSecurityContextExecutorService createExecutor(ScheduledExecutorService delegate) { + return new DelegatingSecurityContextExecutorService(delegate, securityContext()); + } + + private static SecurityContext securityContext() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); + + return securityContext; + } + +} diff --git a/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextScheduledExecutorServiceIntegrationTests.java b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextScheduledExecutorServiceIntegrationTests.java new file mode 100644 index 0000000000..0a6ddc2dd5 --- /dev/null +++ b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextScheduledExecutorServiceIntegrationTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020-2023 the original author or authors. + * + * 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 + * + * https://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 org.springframework.security.concurrent; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.core.task.VirtualThreadTaskExecutor; +import org.springframework.security.DelegatingSecurityContextTestUtils; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Riesenberg + */ +public class DelegatingSecurityContextScheduledExecutorServiceIntegrationTests { + + @Test + public void executeWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void executeWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext executeAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn( + threadFactory, + this::createExecutor, + ScheduledExecutorService::execute + ); + // @formatter:on + } + + @Test + public void submitWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void submitWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext submitAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.callAndReturn( + threadFactory, + this::createExecutor, + ScheduledExecutorService::submit + ); + // @formatter:on + } + + @Test + public void scheduleWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = scheduleAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void scheduleWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = scheduleAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext scheduleAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.callAndReturn( + threadFactory, + this::createExecutor, + (executor, task) -> executor.schedule(task, 50, TimeUnit.MILLISECONDS) + ); + // @formatter:on + } + + private DelegatingSecurityContextScheduledExecutorService createExecutor(ScheduledExecutorService delegate) { + return new DelegatingSecurityContextScheduledExecutorService(delegate, securityContext()); + } + + private static SecurityContext securityContext() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); + + return securityContext; + } + +} diff --git a/core/src/test/java/org/springframework/security/scheduling/DelegatingSecurityContextSchedulingTaskExecutorIntegrationTests.java b/core/src/test/java/org/springframework/security/scheduling/DelegatingSecurityContextSchedulingTaskExecutorIntegrationTests.java new file mode 100644 index 0000000000..e77a7cffa5 --- /dev/null +++ b/core/src/test/java/org/springframework/security/scheduling/DelegatingSecurityContextSchedulingTaskExecutorIntegrationTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2020-2023 the original author or authors. + * + * 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 + * + * https://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 org.springframework.security.scheduling; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.core.task.VirtualThreadTaskExecutor; +import org.springframework.scheduling.SchedulingTaskExecutor; +import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; +import org.springframework.security.DelegatingSecurityContextTestUtils; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Riesenberg + */ +public class DelegatingSecurityContextSchedulingTaskExecutorIntegrationTests { + + @Test + public void executeWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void executeWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext executeAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn( + threadFactory, + this::createExecutor, + SchedulingTaskExecutor::execute + ); + // @formatter:on + } + + @Test + public void executeCompletableWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeCompletableAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void executeCompletableWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeCompletableAndReturn( + new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext executeCompletableAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn( + threadFactory, + this::createExecutor, + SchedulingTaskExecutor::submitCompletable + ); + // @formatter:on + } + + @Test + public void submitWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void submitWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext submitAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.callAndReturn( + threadFactory, + this::createExecutor, + SchedulingTaskExecutor::submit + ); + // @formatter:on + } + + @Test + public void submitCompletableWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitCompletableAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void submitCompletableWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitCompletableAndReturn( + new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext submitCompletableAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.callAndReturn( + threadFactory, + this::createExecutor, + SchedulingTaskExecutor::submitCompletable + ); + // @formatter:on + } + + private DelegatingSecurityContextSchedulingTaskExecutor createExecutor(ScheduledExecutorService delegate) { + return new DelegatingSecurityContextSchedulingTaskExecutor(new ConcurrentTaskExecutor(delegate), + securityContext()); + } + + private static SecurityContext securityContext() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); + + return securityContext; + } + +} diff --git a/core/src/test/java/org/springframework/security/scheduling/DelegatingSecurityContextTaskSchedulerIntegrationTests.java b/core/src/test/java/org/springframework/security/scheduling/DelegatingSecurityContextTaskSchedulerIntegrationTests.java new file mode 100644 index 0000000000..aff48a6d8a --- /dev/null +++ b/core/src/test/java/org/springframework/security/scheduling/DelegatingSecurityContextTaskSchedulerIntegrationTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2020-2023 the original author or authors. + * + * 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 + * + * https://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 org.springframework.security.scheduling; + +import java.time.Duration; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.core.task.VirtualThreadTaskExecutor; +import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; +import org.springframework.scheduling.support.PeriodicTrigger; +import org.springframework.security.DelegatingSecurityContextTestUtils; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Riesenberg + */ +public class DelegatingSecurityContextTaskSchedulerIntegrationTests { + + @Test + public void scheduleWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = scheduleAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void scheduleWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = scheduleAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext scheduleAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn( + threadFactory, + this::createTaskScheduler, + (taskScheduler, task) -> taskScheduler.schedule(task, new PeriodicTrigger(Duration.ofMillis(50))) + ); + // @formatter:on + } + + @Test + public void scheduleAtFixedRateWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = scheduleAtFixedRateAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void scheduleAtFixedRateWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = scheduleAtFixedRateAndReturn( + new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext scheduleAtFixedRateAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn( + threadFactory, + this::createTaskScheduler, + (taskScheduler, task) -> taskScheduler.scheduleAtFixedRate(task, Duration.ofMillis(50)) + ); + // @formatter:on + } + + @Test + public void scheduleWithFixedDelayWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = scheduleWithFixedDelayAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void scheduleWithFixedDelayWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = scheduleWithFixedDelayAndReturn( + new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext scheduleWithFixedDelayAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn( + threadFactory, + this::createTaskScheduler, + (taskScheduler, task) -> taskScheduler.scheduleWithFixedDelay(task, Duration.ofMillis(50)) + ); + // @formatter:on + } + + private DelegatingSecurityContextTaskScheduler createTaskScheduler(ScheduledExecutorService delegate) { + return new DelegatingSecurityContextTaskScheduler(new ConcurrentTaskScheduler(delegate), securityContext()); + } + + private static SecurityContext securityContext() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); + + return securityContext; + } + +} diff --git a/core/src/test/java/org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutorIntegrationTests.java b/core/src/test/java/org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutorIntegrationTests.java new file mode 100644 index 0000000000..1fd44da083 --- /dev/null +++ b/core/src/test/java/org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutorIntegrationTests.java @@ -0,0 +1,143 @@ +/* + * Copyright 2020-2023 the original author or authors. + * + * 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 + * + * https://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 org.springframework.security.task; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.VirtualThreadTaskExecutor; +import org.springframework.core.task.support.TaskExecutorAdapter; +import org.springframework.security.DelegatingSecurityContextTestUtils; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Riesenberg + */ +public class DelegatingSecurityContextAsyncTaskExecutorIntegrationTests { + + @Test + public void executeWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void executeWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext executeAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn(threadFactory, + this::createExecutor, + AsyncTaskExecutor::execute + ); + // @formatter:on + } + + @Test + public void executeCompletableWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeCompletableAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void executeCompletableWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeCompletableAndReturn( + new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext executeCompletableAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn(threadFactory, + this::createExecutor, + AsyncTaskExecutor::submitCompletable + ); + // @formatter:on + } + + @Test + public void submitWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void submitWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext submitAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.callAndReturn(threadFactory, + this::createExecutor, + AsyncTaskExecutor::submit + ); + // @formatter:on + } + + @Test + public void submitCompletableWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitCompletableAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void submitCompletableWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = submitCompletableAndReturn( + new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext submitCompletableAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.callAndReturn(threadFactory, + this::createExecutor, + AsyncTaskExecutor::submitCompletable + ); + // @formatter:on + } + + private DelegatingSecurityContextAsyncTaskExecutor createExecutor(ScheduledExecutorService delegate) { + return new DelegatingSecurityContextAsyncTaskExecutor(new TaskExecutorAdapter(delegate), securityContext()); + } + + private static SecurityContext securityContext() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); + + return securityContext; + } + +} diff --git a/core/src/test/java/org/springframework/security/task/DelegatingSecurityContextTaskExecutorIntegrationTests.java b/core/src/test/java/org/springframework/security/task/DelegatingSecurityContextTaskExecutorIntegrationTests.java new file mode 100644 index 0000000000..6d0f1a992c --- /dev/null +++ b/core/src/test/java/org/springframework/security/task/DelegatingSecurityContextTaskExecutorIntegrationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020-2023 the original author or authors. + * + * 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 + * + * https://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 org.springframework.security.task; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; + +import org.springframework.core.task.TaskExecutor; +import org.springframework.core.task.VirtualThreadTaskExecutor; +import org.springframework.core.task.support.TaskExecutorAdapter; +import org.springframework.security.DelegatingSecurityContextTestUtils; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Riesenberg + */ +public class DelegatingSecurityContextTaskExecutorIntegrationTests { + + @Test + public void executeWhenThreadFactoryIsPlatformThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(Executors.defaultThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + @Test + @DisabledOnJre(JRE.JAVA_17) + public void executeWhenThreadFactoryIsVirtualThenSecurityContextPropagated() throws Exception { + SecurityContext securityContext = executeAndReturn(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); + assertThat(securityContext.getAuthentication()).isNotNull(); + } + + private SecurityContext executeAndReturn(ThreadFactory threadFactory) throws Exception { + // @formatter:off + return DelegatingSecurityContextTestUtils.runAndReturn(threadFactory, + this::createExecutor, + TaskExecutor::execute + ); + // @formatter:on + } + + private DelegatingSecurityContextTaskExecutor createExecutor(ScheduledExecutorService delegate) { + return new DelegatingSecurityContextTaskExecutor(new TaskExecutorAdapter(delegate), securityContext()); + } + + private static SecurityContext securityContext() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); + + return securityContext; + } + +}