1   /* Copyright 2004, 2005 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.ui.basicauth;
17  
18  import junit.framework.TestCase;
19  
20  import org.acegisecurity.MockAuthenticationEntryPoint;
21  import org.acegisecurity.MockAuthenticationManager;
22  import org.acegisecurity.MockFilterConfig;
23  import org.acegisecurity.context.SecurityContextHolder;
24  import org.acegisecurity.context.SecurityContextImpl;
25  import org.acegisecurity.userdetails.UserDetails;
26  
27  import org.apache.commons.codec.binary.Base64;
28  
29  import org.springframework.context.ApplicationContext;
30  import org.springframework.context.support.ClassPathXmlApplicationContext;
31  
32  import org.springframework.mock.web.MockHttpServletRequest;
33  import org.springframework.mock.web.MockHttpServletResponse;
34  import org.springframework.mock.web.MockHttpSession;
35  
36  import java.io.IOException;
37  
38  import javax.servlet.Filter;
39  import javax.servlet.FilterChain;
40  import javax.servlet.FilterConfig;
41  import javax.servlet.ServletException;
42  import javax.servlet.ServletRequest;
43  import javax.servlet.ServletResponse;
44  
45  
46  /***
47   * Tests {@link BasicProcessingFilter}.
48   *
49   * @author Ben Alex
50   * @version $Id: BasicProcessingFilterTests.java,v 1.14 2005/11/29 13:10:09 benalex Exp $
51   */
52  public class BasicProcessingFilterTests extends TestCase {
53      //~ Constructors ===========================================================
54  
55      public BasicProcessingFilterTests() {
56          super();
57      }
58  
59      public BasicProcessingFilterTests(String arg0) {
60          super(arg0);
61      }
62  
63      //~ Methods ================================================================
64  
65      public static void main(String[] args) {
66          junit.textui.TestRunner.run(BasicProcessingFilterTests.class);
67      }
68  
69      public void testDoFilterWithNonHttpServletRequestDetected()
70          throws Exception {
71          BasicProcessingFilter filter = new BasicProcessingFilter();
72  
73          try {
74              filter.doFilter(null, new MockHttpServletResponse(),
75                  new MockFilterChain());
76              fail("Should have thrown ServletException");
77          } catch (ServletException expected) {
78              assertEquals("Can only process HttpServletRequest",
79                  expected.getMessage());
80          }
81      }
82  
83      public void testDoFilterWithNonHttpServletResponseDetected()
84          throws Exception {
85          BasicProcessingFilter filter = new BasicProcessingFilter();
86  
87          try {
88              filter.doFilter(new MockHttpServletRequest(null, null), null,
89                  new MockFilterChain());
90              fail("Should have thrown ServletException");
91          } catch (ServletException expected) {
92              assertEquals("Can only process HttpServletResponse",
93                  expected.getMessage());
94          }
95      }
96  
97      public void testFilterIgnoresRequestsContainingNoAuthorizationHeader()
98          throws Exception {
99          // Setup our HTTP request
100         MockHttpServletRequest request = new MockHttpServletRequest();
101         request.setServletPath("/some_file.html");
102 
103         // Launch an application context and access our bean
104         ApplicationContext ctx = new ClassPathXmlApplicationContext(
105                 "org/acegisecurity/ui/basicauth/filtertest-valid.xml");
106         BasicProcessingFilter filter = (BasicProcessingFilter) ctx.getBean(
107                 "basicProcessingFilter");
108 
109         // Setup our filter configuration
110         MockFilterConfig config = new MockFilterConfig();
111 
112         // Setup our expectation that the filter chain will be invoked
113         MockFilterChain chain = new MockFilterChain(true);
114         MockHttpServletResponse response = new MockHttpServletResponse();
115 
116         // Test
117         executeFilterInContainerSimulator(config, filter, request, response,
118             chain);
119 
120         assertNull(SecurityContextHolder.getContext().getAuthentication());
121     }
122 
123     public void testGettersSetters() {
124         BasicProcessingFilter filter = new BasicProcessingFilter();
125         filter.setAuthenticationManager(new MockAuthenticationManager());
126         assertTrue(filter.getAuthenticationManager() != null);
127 
128         filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
129                 "sx"));
130         assertTrue(filter.getAuthenticationEntryPoint() != null);
131     }
132 
133     public void testInvalidBasicAuthorizationTokenIsIgnored()
134         throws Exception {
135         // Setup our HTTP request
136         String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
137         MockHttpServletRequest request = new MockHttpServletRequest();
138         request.addHeader("Authorization",
139             "Basic " + new String(Base64.encodeBase64(token.getBytes())));
140         request.setServletPath("/some_file.html");
141         request.setSession(new MockHttpSession());
142 
143         // Launch an application context and access our bean
144         ApplicationContext ctx = new ClassPathXmlApplicationContext(
145                 "org/acegisecurity/ui/basicauth/filtertest-valid.xml");
146         BasicProcessingFilter filter = (BasicProcessingFilter) ctx.getBean(
147                 "basicProcessingFilter");
148 
149         // Setup our filter configuration
150         MockFilterConfig config = new MockFilterConfig();
151 
152         // Setup our expectation that the filter chain will be invoked
153         MockFilterChain chain = new MockFilterChain(true);
154         MockHttpServletResponse response = new MockHttpServletResponse();
155 
156         // Test
157         executeFilterInContainerSimulator(config, filter, request, response,
158             chain);
159 
160         assertNull(SecurityContextHolder.getContext().getAuthentication());
161     }
162 
163     public void testNormalOperation() throws Exception {
164         // Setup our HTTP request
165         String token = "marissa:koala";
166         MockHttpServletRequest request = new MockHttpServletRequest();
167         request.addHeader("Authorization",
168             "Basic " + new String(Base64.encodeBase64(token.getBytes())));
169         request.setServletPath("/some_file.html");
170         request.setSession(new MockHttpSession());
171 
172         // Launch an application context and access our bean
173         ApplicationContext ctx = new ClassPathXmlApplicationContext(
174                 "org/acegisecurity/ui/basicauth/filtertest-valid.xml");
175         BasicProcessingFilter filter = (BasicProcessingFilter) ctx.getBean(
176                 "basicProcessingFilter");
177 
178         // Setup our filter configuration
179         MockFilterConfig config = new MockFilterConfig();
180 
181         // Setup our expectation that the filter chain will be invoked
182         MockFilterChain chain = new MockFilterChain(true);
183         MockHttpServletResponse response = new MockHttpServletResponse();
184 
185         // Test
186         assertNull(SecurityContextHolder.getContext().getAuthentication());
187         executeFilterInContainerSimulator(config, filter, request, response,
188             chain);
189 
190         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
191         assertEquals("marissa",
192             ((UserDetails) SecurityContextHolder.getContext().getAuthentication()
193                                                 .getPrincipal()).getUsername());
194     }
195 
196     public void testOtherAuthorizationSchemeIsIgnored()
197         throws Exception {
198         // Setup our HTTP request
199         MockHttpServletRequest request = new MockHttpServletRequest();
200         request.addHeader("Authorization", "SOME_OTHER_AUTHENTICATION_SCHEME");
201         request.setServletPath("/some_file.html");
202 
203         // Launch an application context and access our bean
204         ApplicationContext ctx = new ClassPathXmlApplicationContext(
205                 "org/acegisecurity/ui/basicauth/filtertest-valid.xml");
206         BasicProcessingFilter filter = (BasicProcessingFilter) ctx.getBean(
207                 "basicProcessingFilter");
208 
209         // Setup our filter configuration
210         MockFilterConfig config = new MockFilterConfig();
211 
212         // Setup our expectation that the filter chain will be invoked
213         MockFilterChain chain = new MockFilterChain(true);
214         MockHttpServletResponse response = new MockHttpServletResponse();
215 
216         // Test
217         executeFilterInContainerSimulator(config, filter, request, response,
218             chain);
219 
220         assertNull(SecurityContextHolder.getContext().getAuthentication());
221     }
222 
223     public void testStartupDetectsMissingAuthenticationEntryPoint()
224         throws Exception {
225         try {
226             BasicProcessingFilter filter = new BasicProcessingFilter();
227             filter.setAuthenticationManager(new MockAuthenticationManager());
228             filter.afterPropertiesSet();
229             fail("Should have thrown IllegalArgumentException");
230         } catch (IllegalArgumentException expected) {
231             assertEquals("An AuthenticationEntryPoint is required",
232                 expected.getMessage());
233         }
234     }
235 
236     public void testStartupDetectsMissingAuthenticationManager()
237         throws Exception {
238         try {
239             BasicProcessingFilter filter = new BasicProcessingFilter();
240             filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
241                     "x"));
242             filter.afterPropertiesSet();
243             fail("Should have thrown IllegalArgumentException");
244         } catch (IllegalArgumentException expected) {
245             assertEquals("An AuthenticationManager is required",
246                 expected.getMessage());
247         }
248     }
249 
250     public void testSuccessLoginThenFailureLoginResultsInSessionLoosingToken()
251         throws Exception {
252         // Setup our HTTP request
253         String token = "marissa:koala";
254         MockHttpServletRequest request = new MockHttpServletRequest();
255         request.addHeader("Authorization",
256             "Basic " + new String(Base64.encodeBase64(token.getBytes())));
257         request.setServletPath("/some_file.html");
258         request.setSession(new MockHttpSession());
259 
260         // Launch an application context and access our bean
261         ApplicationContext ctx = new ClassPathXmlApplicationContext(
262                 "org/acegisecurity/ui/basicauth/filtertest-valid.xml");
263         BasicProcessingFilter filter = (BasicProcessingFilter) ctx.getBean(
264                 "basicProcessingFilter");
265 
266         // Setup our filter configuration
267         MockFilterConfig config = new MockFilterConfig();
268 
269         // Setup our expectation that the filter chain will be invoked
270         MockFilterChain chain = new MockFilterChain(true);
271         MockHttpServletResponse response = new MockHttpServletResponse();
272 
273         // Test
274         executeFilterInContainerSimulator(config, filter, request, response,
275             chain);
276 
277         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
278         assertEquals("marissa",
279             ((UserDetails) SecurityContextHolder.getContext().getAuthentication()
280                                                 .getPrincipal()).getUsername());
281 
282         // NOW PERFORM FAILED AUTHENTICATION
283         // Setup our HTTP request
284         token = "otherUser:WRONG_PASSWORD";
285         request = new MockHttpServletRequest();
286         request.addHeader("Authorization",
287             "Basic " + new String(Base64.encodeBase64(token.getBytes())));
288         request.setServletPath("/some_file.html");
289         request.setSession(new MockHttpSession());
290 
291         // Setup our expectation that the filter chain will not be invoked, as we get a 403 forbidden response
292         chain = new MockFilterChain(false);
293         response = new MockHttpServletResponse();
294 
295         // Test
296         executeFilterInContainerSimulator(config, filter, request, response,
297             chain);
298 
299         assertNull(SecurityContextHolder.getContext().getAuthentication());
300         assertEquals(401, response.getStatus());
301     }
302 
303     public void testWrongPasswordReturnsForbidden() throws Exception {
304         // Setup our HTTP request
305         String token = "marissa:WRONG_PASSWORD";
306         MockHttpServletRequest request = new MockHttpServletRequest();
307         request.addHeader("Authorization",
308             "Basic " + new String(Base64.encodeBase64(token.getBytes())));
309         request.setServletPath("/some_file.html");
310         request.setSession(new MockHttpSession());
311 
312         // Launch an application context and access our bean
313         ApplicationContext ctx = new ClassPathXmlApplicationContext(
314                 "org/acegisecurity/ui/basicauth/filtertest-valid.xml");
315         BasicProcessingFilter filter = (BasicProcessingFilter) ctx.getBean(
316                 "basicProcessingFilter");
317 
318         // Setup our filter configuration
319         MockFilterConfig config = new MockFilterConfig();
320 
321         // Setup our expectation that the filter chain will not be invoked, as we get a 403 forbidden response
322         MockFilterChain chain = new MockFilterChain(false);
323         MockHttpServletResponse response = new MockHttpServletResponse();
324 
325         // Test
326         executeFilterInContainerSimulator(config, filter, request, response,
327             chain);
328 
329         assertNull(SecurityContextHolder.getContext().getAuthentication());
330         assertEquals(401, response.getStatus());
331     }
332 
333     protected void setUp() throws Exception {
334         super.setUp();
335         SecurityContextHolder.setContext(new SecurityContextImpl());
336     }
337 
338     protected void tearDown() throws Exception {
339         super.tearDown();
340         SecurityContextHolder.setContext(new SecurityContextImpl());
341     }
342 
343     private void executeFilterInContainerSimulator(FilterConfig filterConfig,
344         Filter filter, ServletRequest request, ServletResponse response,
345         FilterChain filterChain) throws ServletException, IOException {
346         filter.init(filterConfig);
347         filter.doFilter(request, response, filterChain);
348         filter.destroy();
349     }
350 
351     //~ Inner Classes ==========================================================
352 
353     private class MockFilterChain implements FilterChain {
354         private boolean expectToProceed;
355 
356         public MockFilterChain(boolean expectToProceed) {
357             this.expectToProceed = expectToProceed;
358         }
359 
360         private MockFilterChain() {
361             super();
362         }
363 
364         public void doFilter(ServletRequest request, ServletResponse response)
365             throws IOException, ServletException {
366             if (expectToProceed) {
367                 assertTrue(true);
368             } else {
369                 fail("Did not expect filter chain to proceed");
370             }
371         }
372     }
373 }