/* * The MIT License * * Copyright (c) 2013, Patrick McKeown * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package jenkins.security; import hudson.model.User; import hudson.security.ACL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import java.util.Collection; import java.util.LinkedList; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.ScheduledThreadPoolExecutor; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.recipes.PresetData; /** * @author Patrick McKeown */ public class SecurityContextExecutorServiceTest { final private int NUM_THREADS = 10; private ExecutorService wrappedService = null; private SecurityContext systemContext = null; private SecurityContext userContext = null; private SecurityContext nullContext = null; private volatile SecurityContext runnableThreadContext; @Rule public JenkinsRule j = new JenkinsRule() { public void before() throws Throwable { setPluginManager(null); super.before(); ScheduledThreadPoolExecutor service = new ScheduledThreadPoolExecutor(NUM_THREADS); // Create a system level context with ACL.SYSTEM systemContext = ACL.impersonate(ACL.SYSTEM); User u = User.get("bob"); // Create a sample user context userContext = new NonSerializableSecurityContext(u.impersonate()); // Create a null context SecurityContextHolder.clearContext(); nullContext = SecurityContextHolder.getContext(); // Create a wrapped service wrappedService = new SecurityContextExecutorService(service); } }; @Test @PresetData(PresetData.DataSet.NO_ANONYMOUS_READACCESS) public void testRunnableAgainstAllContexts() throws Exception { Runnable r = new Runnable() { public void run() { runnableThreadContext = SecurityContextHolder.getContext(); } }; SecurityContextHolder.setContext(systemContext); Future systemResult = wrappedService.submit(r); // Assert the runnable completed successfully assertNull(systemResult.get()); // Assert the context inside the runnable thread was set to ACL.SYSTEM assertEquals(systemContext, runnableThreadContext); SecurityContextHolder.setContext(userContext); Future userResult = wrappedService.submit(r); // Assert the runnable completed successfully assertNull(userResult.get()); // Assert the context inside the runnable thread was set to the user's context assertEquals(userContext, runnableThreadContext); SecurityContextHolder.setContext(nullContext); Future nullResult = wrappedService.submit(r); // Assert the runnable completed successfully assertNull(nullResult.get()); // Assert the context inside the runnable thread was set to the null context assertEquals(nullContext, runnableThreadContext); } @Test @PresetData(PresetData.DataSet.NO_ANONYMOUS_READACCESS) public void testCallableAgainstAllContexts() throws Exception { Callable<SecurityContext> c = new Callable<SecurityContext>() { public SecurityContext call() throws Exception { return SecurityContextHolder.getContext(); } }; SecurityContextHolder.setContext(systemContext); Future<SecurityContext> result = wrappedService.submit(c); // Assert the context inside the callable thread was set to ACL.SYSTEM assertEquals(systemContext, result.get()); SecurityContextHolder.setContext(userContext); result = wrappedService.submit(c); // Assert the context inside the callable thread was set to the user's context assertEquals(userContext, result.get()); SecurityContextHolder.setContext(nullContext); result = wrappedService.submit(c); // Assert the context inside the callable thread was set to the null context assertEquals(nullContext, result.get()); } @Test @PresetData(PresetData.DataSet.NO_ANONYMOUS_READACCESS) public void testCallableCollectionAgainstAllContexts() throws Exception { Collection<Callable<SecurityContext>> callables = new LinkedList<Callable<SecurityContext>>(); Callable<SecurityContext> c = new Callable<SecurityContext>() { public SecurityContext call() throws Exception { return SecurityContextHolder.getContext(); } }; callables.add(c); callables.add(c); callables.add(c); SecurityContextHolder.setContext(systemContext); Collection<Future<SecurityContext>> results = wrappedService.invokeAll(callables); for (Future<SecurityContext> result : results) { // Assert each thread context was identical to the initial service context SecurityContext value = result.get(); assertEquals(systemContext, value); } SecurityContextHolder.setContext(userContext); results = wrappedService.invokeAll(callables); for (Future<SecurityContext> result : results) { // Assert each thread context was identical to the initial service context assertEquals(userContext, result.get()); } SecurityContextHolder.setContext(nullContext); results = wrappedService.invokeAll(callables); for (Future<SecurityContext> result : results) { // Assert each thread context was identical to the initial service context assertEquals(nullContext, result.get()); } } @Test @PresetData(PresetData.DataSet.NO_ANONYMOUS_READACCESS) public void testFailedRunnableResetsContext() throws Exception { Runnable r = new Runnable() { public void run() { SecurityContextHolder.setContext(nullContext); throw new RuntimeException("Simulate a failure"); } }; SecurityContextHolder.setContext(systemContext); try { wrappedService.execute(r); } catch (AssertionError expectedException) { // Assert the current context is once again ACL.SYSTEM assertEquals(systemContext, SecurityContextHolder.getContext()); } SecurityContextHolder.setContext(userContext); try { wrappedService.execute(r); } catch (AssertionError expectedException) { // Assert the current context is once again the userContext assertEquals(userContext, SecurityContextHolder.getContext()); } } }