1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.context;
17  
18  import junit.framework.TestCase;
19  
20  import org.acegisecurity.Authentication;
21  import org.acegisecurity.GrantedAuthority;
22  import org.acegisecurity.GrantedAuthorityImpl;
23  import org.acegisecurity.MockFilterConfig;
24  
25  import org.acegisecurity.adapters.PrincipalAcegiUserToken;
26  import org.jmock.Mock;
27  
28  import org.springframework.mock.web.MockHttpServletRequest;
29  import org.springframework.mock.web.MockHttpServletResponse;
30  
31  import java.io.IOException;
32  
33  import javax.servlet.Filter;
34  import javax.servlet.FilterChain;
35  import javax.servlet.FilterConfig;
36  import javax.servlet.ServletException;
37  import javax.servlet.ServletRequest;
38  import javax.servlet.ServletResponse;
39  
40  /**
41   * Tests {@link HttpSessionContextIntegrationFilter}.
42   *
43   * @author Ben Alex
44   * @version $Id: HttpSessionContextIntegrationFilterTests.java 1858 2007-05-24
45   *          02:04:47Z benalex $
46   */
47  public class HttpSessionContextIntegrationFilterTests extends TestCase {
48  	//~ Constructors ===================================================================================================
49  
50  	public HttpSessionContextIntegrationFilterTests() {
51  	}
52  
53  	public HttpSessionContextIntegrationFilterTests(String arg0) {
54  		super(arg0);
55  	}
56  
57  	//~ Methods ========================================================================================================
58  
59  	private static void executeFilterInContainerSimulator(
60  			FilterConfig filterConfig, Filter filter, ServletRequest request,
61  			ServletResponse response, FilterChain filterChain)
62  			throws ServletException, IOException {
63  		filter.init(filterConfig);
64  		filter.doFilter(request, response, filterChain);
65  		filter.destroy();
66  	}
67  
68  	public void testDetectsIncompatibleSessionProperties() throws Exception {
69  		HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
70  
71  		try {
72  			filter.setAllowSessionCreation(false);
73  			filter.setForceEagerSessionCreation(true);
74  			filter.afterPropertiesSet();
75  			fail("Shown have thrown IllegalArgumentException");
76  		} catch (IllegalArgumentException expected) {
77  			assertTrue(true);
78  		}
79  
80  		filter.setAllowSessionCreation(true);
81  		filter.afterPropertiesSet();
82  		assertTrue(true);
83  	}
84  
85  	public void testDetectsMissingOrInvalidContext() throws Exception {
86  		HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
87  
88  		try {
89  			filter.setContext(null);
90  			filter.afterPropertiesSet();
91  			fail("Shown have thrown IllegalArgumentException");
92  		} catch (IllegalArgumentException expected) {
93  			assertTrue(true);
94  		}
95  
96  		try {
97  			filter.setContext(Integer.class);
98  			assertEquals(Integer.class, filter.getContext());
99  			filter.afterPropertiesSet();
100 			fail("Shown have thrown IllegalArgumentException");
101 		} catch (IllegalArgumentException expected) {
102 			assertTrue(true);
103 		}
104 	}
105 
106 	public void testExceptionWithinFilterChainStillClearsSecurityContextHolder() throws Exception {
107 		// Build an Authentication object we simulate came from HttpSession
108 		PrincipalAcegiUserToken sessionPrincipal = new PrincipalAcegiUserToken(
109 				"key",
110 				"someone",
111 				"password",
112 				new GrantedAuthority[] { new GrantedAuthorityImpl("SOME_ROLE") },
113 				null);
114 
115 		// Build a Context to store in HttpSession (simulating prior request)
116 		SecurityContext sc = new SecurityContextImpl();
117 		sc.setAuthentication(sessionPrincipal);
118 
119 		// Build a mock request
120 		MockHttpServletRequest request = new MockHttpServletRequest();
121 		request.getSession().setAttribute(
122 				HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY,
123 				sc);
124 
125 		MockHttpServletResponse response = new MockHttpServletResponse();
126 		FilterChain chain = new MockFilterChain(sessionPrincipal, null,
127 				new IOException());
128 
129 		// Prepare filter
130 		HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
131 		filter.setContext(SecurityContextImpl.class);
132 		filter.afterPropertiesSet();
133 
134 		// Execute filter
135 		try {
136 			executeFilterInContainerSimulator(new MockFilterConfig(), filter,
137 					request, response, chain);
138 			fail("We should have received the IOException thrown inside the filter chain here");
139 		} catch (IOException ioe) {
140 			assertTrue(true);
141 		}
142 
143 		// Check the SecurityContextHolder is null, even though an exception was
144 		// thrown during chain
145 		assertEquals(new SecurityContextImpl(), SecurityContextHolder.getContext());
146 		assertNull("Should have cleared FILTER_APPLIED",
147                 request.getAttribute(HttpSessionContextIntegrationFilter.FILTER_APPLIED));
148 	}
149 
150 	public void testExistingContextContentsCopiedIntoContextHolderFromSessionAndChangesToContextCopiedBackToSession()
151 			throws Exception {
152 		// Build an Authentication object we simulate came from HttpSession
153 		PrincipalAcegiUserToken sessionPrincipal = new PrincipalAcegiUserToken(
154 				"key",
155 				"someone",
156 				"password",
157 				new GrantedAuthority[] { new GrantedAuthorityImpl("SOME_ROLE") },
158 				null);
159 
160 		// Build an Authentication object we simulate our Authentication changed
161 		// it to
162 		PrincipalAcegiUserToken updatedPrincipal = new PrincipalAcegiUserToken(
163 				"key", "someone", "password",
164 				new GrantedAuthority[] { new GrantedAuthorityImpl(
165 						"SOME_DIFFERENT_ROLE") }, null);
166 
167 		// Build a Context to store in HttpSession (simulating prior request)
168 		SecurityContext sc = new SecurityContextImpl();
169 		sc.setAuthentication(sessionPrincipal);
170 
171 		// Build a mock request
172 		MockHttpServletRequest request = new MockHttpServletRequest();
173 		request.getSession().setAttribute(
174 				HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY,
175 				sc);
176 
177 		MockHttpServletResponse response = new MockHttpServletResponse();
178 		FilterChain chain = new MockFilterChain(sessionPrincipal,
179 				updatedPrincipal, null);
180 
181 		// Prepare filter
182 		HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
183 		filter.setContext(SecurityContextImpl.class);
184 		filter.afterPropertiesSet();
185 
186 		// Execute filter
187 		executeFilterInContainerSimulator(new MockFilterConfig(), filter,
188 				request, response, chain);
189 
190 		// Obtain new/update Authentication from HttpSession
191 		SecurityContext context = (SecurityContext) request.getSession().getAttribute(
192 						HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
193 		assertEquals(updatedPrincipal, ((SecurityContext) context).getAuthentication());
194 	}
195 
196 	public void testHttpSessionCreatedWhenContextHolderChanges() throws Exception {
197 		// Build an Authentication object we simulate our Authentication changed it to
198 		PrincipalAcegiUserToken updatedPrincipal = new PrincipalAcegiUserToken(
199 				"key", "someone", "password",
200 				new GrantedAuthority[] { new GrantedAuthorityImpl(
201 						"SOME_DIFFERENT_ROLE") }, null);
202 
203 		// Build a mock request
204 		MockHttpServletRequest request = new MockHttpServletRequest();
205 		MockHttpServletResponse response = new MockHttpServletResponse();
206 		FilterChain chain = new MockFilterChain(null, updatedPrincipal, null);
207 
208 		// Prepare filter
209 		HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
210 		filter.setContext(SecurityContextImpl.class);
211 		// don't call afterPropertiesSet to test case when Spring filter.afterPropertiesSet(); isn't called
212 
213 		// Execute filter
214 		executeFilterInContainerSimulator(new MockFilterConfig(), filter, request, response, chain);
215 
216 		// Obtain new/updated Authentication from HttpSession
217 		SecurityContext context = (SecurityContext) request.getSession(false).getAttribute(
218 						HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
219 		assertEquals(updatedPrincipal, ((SecurityContext) context).getAuthentication());
220 	}
221 
222 	public void testHttpSessionEagerlyCreatedWhenDirected() throws Exception {
223 		// Build a mock request
224 		MockHttpServletRequest request = new MockHttpServletRequest(null, null);
225 		MockHttpServletResponse response = new MockHttpServletResponse();
226 		FilterChain chain = new MockFilterChain(null, null, null);
227 
228 		// Prepare filter
229 		HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
230 		filter.setContext(SecurityContextImpl.class);
231 		filter.setForceEagerSessionCreation(true); // non-default
232 		filter.afterPropertiesSet();
233 
234 		// Execute filter
235 		executeFilterInContainerSimulator(new MockFilterConfig(), filter,
236 				request, response, chain);
237 
238 		// Check the session is not null
239 		assertNotNull(request.getSession(false));
240 	}
241 
242 	public void testHttpSessionNotCreatedUnlessContextHolderChanges() throws Exception {
243 		// Build a mock request
244 		MockHttpServletRequest request = new MockHttpServletRequest(null, null);
245 		MockHttpServletResponse response = new MockHttpServletResponse();
246 		FilterChain chain = new MockFilterChain(null, null, null);
247 
248 		// Prepare filter
249 		HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
250 		filter.setContext(SecurityContextImpl.class);
251 		filter.afterPropertiesSet();
252 
253 		// Execute filter
254 		executeFilterInContainerSimulator(new MockFilterConfig(), filter,
255 				request, response, chain);
256 
257 		// Check the session is null
258 		assertNull(request.getSession(false));
259 	}
260 
261 	public void testHttpSessionWithNonContextInWellKnownLocationIsOverwritten() throws Exception {
262 		// Build an Authentication object we simulate our Authentication changed
263 		// it to
264 		PrincipalAcegiUserToken updatedPrincipal = new PrincipalAcegiUserToken(
265 				"key", "someone", "password",
266 				new GrantedAuthority[] { new GrantedAuthorityImpl(
267 						"SOME_DIFFERENT_ROLE") }, null);
268 
269 		// Build a mock request
270 		MockHttpServletRequest request = new MockHttpServletRequest();
271 		request.getSession().setAttribute(
272 				HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY,
273 				"NOT_A_CONTEXT_OBJECT");
274 
275 		MockHttpServletResponse response = new MockHttpServletResponse();
276 		FilterChain chain = new MockFilterChain(null, updatedPrincipal, null);
277 
278 		// Prepare filter
279 		HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
280 		filter.setContext(SecurityContextImpl.class);
281 		filter.afterPropertiesSet();
282 
283 		// Execute filter
284 		executeFilterInContainerSimulator(new MockFilterConfig(), filter, request, response, chain);
285 
286 		// Obtain new/update Authentication from HttpSession
287 		SecurityContext context = (SecurityContext) request.getSession().getAttribute(
288 						HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
289 		assertEquals(updatedPrincipal, ((SecurityContext) context).getAuthentication());
290 	}
291 
292 	public void testConcurrentThreadsLazilyChangeFilterAppliedValueToTrue() throws Exception {
293 		PrincipalAcegiUserToken sessionPrincipal = new PrincipalAcegiUserToken(
294 				"key",
295 				"someone",
296 				"password",
297 				new GrantedAuthority[] { new GrantedAuthorityImpl("SOME_ROLE") },
298 				null);
299 
300 		// Build a Context to store in HttpSession (simulating prior request)
301 		SecurityContext sc = new SecurityContextImpl();
302 		sc.setAuthentication(sessionPrincipal);
303 
304 		MockHttpServletRequest request = new MockHttpServletRequest();
305 		request.getSession().setAttribute(
306 				HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY,
307 				sc);
308 		MockHttpServletResponse response = new MockHttpServletResponse();
309 
310 		// Prepare filter
311 		HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
312 		filter.setContext(SecurityContextImpl.class);
313 		filter.afterPropertiesSet();
314 
315 		for (int i = 0; i < 3; i++) {
316 			ThreadRunner runner = new ThreadRunner(request, response, filter,
317 					new MockFilterChain(sessionPrincipal, null, null));
318 			runner.start();
319 		}
320 
321 	}
322 
323 	// ~ Inner Classes
324 	// ==================================================================================================
325 
326 	private class MockFilterChain extends TestCase implements FilterChain {
327 		private Authentication changeContextHolder;
328 		private Authentication expectedOnContextHolder;
329 		private IOException toThrowDuringChain;
330 
331 		public MockFilterChain(Authentication expectedOnContextHolder,
332 				Authentication changeContextHolder,
333 				IOException toThrowDuringChain) {
334 			this.expectedOnContextHolder = expectedOnContextHolder;
335 			this.changeContextHolder = changeContextHolder;
336 			this.toThrowDuringChain = toThrowDuringChain;
337 		}
338 
339 		public void doFilter(ServletRequest arg0, ServletResponse arg1) throws IOException, ServletException {
340 			if (expectedOnContextHolder != null) {
341 				assertEquals(expectedOnContextHolder, SecurityContextHolder.getContext().getAuthentication());
342 			}
343 
344 			if (changeContextHolder != null) {
345 				SecurityContext sc = SecurityContextHolder.getContext();
346 				sc.setAuthentication(changeContextHolder);
347 				SecurityContextHolder.setContext(sc);
348 			}
349 
350 			if (toThrowDuringChain != null) {
351 				throw toThrowDuringChain;
352 			}
353 
354 		}
355 	}
356 
357 	private static class ThreadRunner extends Thread {
358 		private MockHttpServletRequest request;
359 		private MockHttpServletResponse response;
360 		private HttpSessionContextIntegrationFilter filter;
361 		private MockFilterChain chain;
362 
363 		public ThreadRunner(MockHttpServletRequest request,
364 				MockHttpServletResponse response,
365 				HttpSessionContextIntegrationFilter filter,
366 				MockFilterChain chain) {
367 			this.request = request;
368 			this.response = response;
369 			this.filter = filter;
370 			this.chain = chain;
371 		}
372 
373 		public void run() {
374 			try {
375 				// Execute filter
376 				executeFilterInContainerSimulator(new MockFilterConfig(), filter, request, response, chain);
377 
378 				// Check the session is not null
379 				assertNotNull(request.getSession(false));
380 			} catch (Exception e) {
381 				e.printStackTrace();
382 			}
383 		}
384 
385 	}
386 }