001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.context;
017:
018: import junit.framework.ComparisonFailure;
019: import junit.framework.TestCase;
020:
021: import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
022:
023: import java.util.Random;
024:
025: /**
026: * Tests {@link SecurityContextHolder}.
027: *
028: * @author Ben Alex
029: * @version $Id: SecurityContextHolderTests.java 1788 2007-03-10 21:34:53Z luke_t $
030: */
031: public class SecurityContextHolderTests extends TestCase {
032: //~ Static fields/initializers =====================================================================================
033:
034: private static int errors = 0;
035:
036: private static final int NUM_OPS = 25;
037: private static final int NUM_THREADS = 10;
038:
039: //~ Constructors ===================================================================================================
040:
041: public SecurityContextHolderTests() {
042: super ();
043: }
044:
045: public SecurityContextHolderTests(String arg0) {
046: super (arg0);
047: }
048:
049: //~ Methods ========================================================================================================
050:
051: private void loadStartAndWaitForThreads(boolean topLevelThread,
052: String prefix, int createThreads,
053: boolean expectAllThreadsToUseIdenticalAuthentication,
054: boolean expectChildrenToShareAuthenticationWithParent) {
055: Thread[] threads = new Thread[createThreads];
056: errors = 0;
057:
058: if (topLevelThread) {
059: // PARENT (TOP-LEVEL) THREAD CREATION
060: if (expectChildrenToShareAuthenticationWithParent) {
061: // An InheritableThreadLocal
062: for (int i = 0; i < threads.length; i++) {
063: if ((i % 2) == 0) {
064: // Don't inject auth into current thread; neither current thread or child will have authentication
065: threads[i] = makeThread(prefix
066: + "Unauth_Parent_" + i, true, false,
067: false, true, null);
068: } else {
069: // Inject auth into current thread, but not child; current thread will have auth, child will also have auth
070: threads[i] = makeThread(prefix + "Auth_Parent_"
071: + i, true, true, false, true, prefix
072: + "Auth_Parent_" + i);
073: }
074: }
075: } else if (expectAllThreadsToUseIdenticalAuthentication) {
076: // A global
077: SecurityContextHolder.getContext().setAuthentication(
078: new UsernamePasswordAuthenticationToken(
079: "GLOBAL_USERNAME", "pass"));
080:
081: for (int i = 0; i < threads.length; i++) {
082: if ((i % 2) == 0) {
083: // Don't inject auth into current thread;both current thread and child will have same authentication
084: threads[i] = makeThread(prefix
085: + "Unauth_Parent_" + i, true, false,
086: true, true, "GLOBAL_USERNAME");
087: } else {
088: // Inject auth into current thread; current thread will have auth, child will also have auth
089: threads[i] = makeThread(prefix + "Auth_Parent_"
090: + i, true, true, true, true,
091: "GLOBAL_USERNAME");
092: }
093: }
094: } else {
095: // A standard ThreadLocal
096: for (int i = 0; i < threads.length; i++) {
097: if ((i % 2) == 0) {
098: // Don't inject auth into current thread; neither current thread or child will have authentication
099: threads[i] = makeThread(prefix
100: + "Unauth_Parent_" + i, true, false,
101: false, false, null);
102: } else {
103: // Inject auth into current thread, but not child; current thread will have auth, child will not have auth
104: threads[i] = makeThread(prefix + "Auth_Parent_"
105: + i, true, true, false, false, prefix
106: + "Auth_Parent_" + i);
107: }
108: }
109: }
110: } else {
111: // CHILD THREAD CREATION
112: if (expectChildrenToShareAuthenticationWithParent
113: || expectAllThreadsToUseIdenticalAuthentication) {
114: // The children being created are all expected to have security (ie an InheritableThreadLocal/global AND auth was injected into parent)
115: for (int i = 0; i < threads.length; i++) {
116: String expectedUsername = prefix;
117:
118: if (expectAllThreadsToUseIdenticalAuthentication) {
119: expectedUsername = "GLOBAL_USERNAME";
120: }
121:
122: // Don't inject auth into current thread; the current thread will obtain auth from its parent
123: // NB: As topLevelThread = true, no further child threads will be created
124: threads[i] = makeThread(
125: prefix + "->child->Inherited_Auth_Child_"
126: + i,
127: false,
128: false,
129: expectAllThreadsToUseIdenticalAuthentication,
130: false, expectedUsername);
131: }
132: } else {
133: // The children being created are NOT expected to have security (ie not an InheritableThreadLocal OR auth was not injected into parent)
134: for (int i = 0; i < threads.length; i++) {
135: // Don't inject auth into current thread; neither current thread or child will have authentication
136: // NB: As topLevelThread = true, no further child threads will be created
137: threads[i] = makeThread(prefix
138: + "->child->Unauth_Child_" + i, false,
139: false, false, false, null);
140: }
141: }
142: }
143:
144: // Start and execute the threads
145: startAndRun(threads);
146: }
147:
148: public static void main(String[] args) {
149: junit.textui.TestRunner.run(SecurityContextHolderTests.class);
150: }
151:
152: private Thread makeThread(
153: final String threadIdentifier,
154: final boolean topLevelThread,
155: final boolean injectAuthIntoCurrentThread,
156: final boolean expectAllThreadsToUseIdenticalAuthentication,
157: final boolean expectChildrenToShareAuthenticationWithParent,
158: final String expectedUsername) {
159: final Random rnd = new Random();
160:
161: Thread t = new Thread(new Runnable() {
162: public void run() {
163: if (injectAuthIntoCurrentThread) {
164: // Set authentication in this thread
165: SecurityContextHolder
166: .getContext()
167: .setAuthentication(
168: new UsernamePasswordAuthenticationToken(
169: expectedUsername, "pass"));
170:
171: //System.out.println(threadIdentifier + " - set to " + SecurityContextHolder.getContext().getAuthentication());
172: } else {
173: //System.out.println(threadIdentifier + " - not set (currently " + SecurityContextHolder.getContext().getAuthentication() + ")");
174: }
175:
176: // Do some operations in current thread, checking authentication is as expected in the current thread (ie another thread doesn't change it)
177: for (int i = 0; i < NUM_OPS; i++) {
178: String currentUsername = (SecurityContextHolder
179: .getContext().getAuthentication() == null) ? null
180: : SecurityContextHolder.getContext()
181: .getAuthentication().getName();
182:
183: if ((i % 7) == 0) {
184: System.out.println(threadIdentifier + " at "
185: + i + " username " + currentUsername);
186: }
187:
188: try {
189: TestCase
190: .assertEquals(
191: "Failed on iteration "
192: + i
193: + "; Authentication was '"
194: + currentUsername
195: + "' but principal was expected to contain username '"
196: + expectedUsername
197: + "'",
198: expectedUsername,
199: currentUsername);
200: } catch (ComparisonFailure err) {
201: errors++;
202: throw err;
203: }
204:
205: try {
206: Thread.sleep(rnd.nextInt(250));
207: } catch (InterruptedException ignore) {
208: }
209: }
210:
211: // Load some children threads, checking the authentication is as expected in the children (ie another thread doesn't change it)
212: if (topLevelThread) {
213: // Make four children, but we don't want the children to have any more children (so anti-nature, huh?)
214: if (injectAuthIntoCurrentThread
215: && expectChildrenToShareAuthenticationWithParent) {
216: loadStartAndWaitForThreads(
217: false,
218: threadIdentifier,
219: 4,
220: expectAllThreadsToUseIdenticalAuthentication,
221: true);
222: } else {
223: loadStartAndWaitForThreads(
224: false,
225: threadIdentifier,
226: 4,
227: expectAllThreadsToUseIdenticalAuthentication,
228: false);
229: }
230: }
231: }
232: }, threadIdentifier);
233:
234: return t;
235: }
236:
237: public final void setUp() throws Exception {
238: SecurityContextHolder
239: .setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
240: }
241:
242: private void startAndRun(Thread[] threads) {
243: // Start them up
244: for (int i = 0; i < threads.length; i++) {
245: threads[i].start();
246: }
247:
248: // Wait for them to finish
249: while (stillRunning(threads)) {
250: try {
251: Thread.sleep(250);
252: } catch (InterruptedException ignore) {
253: }
254: }
255: }
256:
257: private boolean stillRunning(Thread[] threads) {
258: for (int i = 0; i < threads.length; i++) {
259: if (threads[i].isAlive()) {
260: return true;
261: }
262: }
263:
264: return false;
265: }
266:
267: public void testContextHolderGetterSetterClearer() {
268: SecurityContext sc = new SecurityContextImpl();
269: sc.setAuthentication(new UsernamePasswordAuthenticationToken(
270: "Foobar", "pass"));
271: SecurityContextHolder.setContext(sc);
272: assertEquals(sc, SecurityContextHolder.getContext());
273: SecurityContextHolder.clearContext();
274: assertNotSame(sc, SecurityContextHolder.getContext());
275: SecurityContextHolder.clearContext();
276: }
277:
278: public void testNeverReturnsNull() {
279: assertNotNull(SecurityContextHolder.getContext());
280: SecurityContextHolder.clearContext();
281: }
282:
283: public void testRejectsNulls() {
284: try {
285: SecurityContextHolder.setContext(null);
286: fail("Should have rejected null");
287: } catch (IllegalArgumentException expected) {
288: assertTrue(true);
289: }
290: }
291:
292: public void testSynchronizationCustomStrategyLoading() {
293: SecurityContextHolder
294: .setStrategyName(InheritableThreadLocalSecurityContextHolderStrategy.class
295: .getName());
296: assertTrue(new SecurityContextHolder()
297: .toString()
298: .lastIndexOf(
299: "SecurityContextHolder[strategy='org.acegisecurity.context.InheritableThreadLocalSecurityContextHolderStrategy'") != -1);
300: loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, false,
301: true);
302: assertEquals(
303: "Thread errors detected; review log output for details",
304: 0, errors);
305: }
306:
307: public void testSynchronizationGlobal() throws Exception {
308: SecurityContextHolder.clearContext();
309: SecurityContextHolder
310: .setStrategyName(SecurityContextHolder.MODE_GLOBAL);
311: loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, true,
312: false);
313: assertEquals(
314: "Thread errors detected; review log output for details",
315: 0, errors);
316: }
317:
318: public void testSynchronizationInheritableThreadLocal()
319: throws Exception {
320: SecurityContextHolder.clearContext();
321: SecurityContextHolder
322: .setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
323: loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, false,
324: true);
325: assertEquals(
326: "Thread errors detected; review log output for details",
327: 0, errors);
328: }
329:
330: public void testSynchronizationThreadLocal() throws Exception {
331: SecurityContextHolder.clearContext();
332: SecurityContextHolder
333: .setStrategyName(SecurityContextHolder.MODE_THREADLOCAL);
334: loadStartAndWaitForThreads(true, "Main_", NUM_THREADS, false,
335: false);
336: assertEquals(
337: "Thread errors detected; review log output for details",
338: 0, errors);
339: }
340: }
|