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.digestauth;
17  
18  import junit.framework.TestCase;
19  
20  import org.acegisecurity.DisabledException;
21  import org.acegisecurity.MockFilterConfig;
22  
23  import org.acegisecurity.context.SecurityContextHolder;
24  import org.acegisecurity.context.SecurityContextImpl;
25  
26  import org.acegisecurity.providers.dao.UserCache;
27  
28  import org.acegisecurity.userdetails.UserDetailsService;
29  import org.acegisecurity.userdetails.UserDetails;
30  import org.acegisecurity.userdetails.UsernameNotFoundException;
31  import org.acegisecurity.util.StringSplitUtils;
32  
33  import org.apache.commons.codec.binary.Base64;
34  
35  import org.springframework.context.ApplicationContext;
36  import org.springframework.context.support.ClassPathXmlApplicationContext;
37  
38  import org.springframework.dao.DataAccessException;
39  
40  import org.springframework.mock.web.MockHttpServletRequest;
41  import org.springframework.mock.web.MockHttpServletResponse;
42  
43  import org.springframework.util.StringUtils;
44  
45  import java.io.IOException;
46  
47  import java.util.Map;
48  
49  import javax.servlet.Filter;
50  import javax.servlet.FilterChain;
51  import javax.servlet.FilterConfig;
52  import javax.servlet.ServletException;
53  import javax.servlet.ServletRequest;
54  import javax.servlet.ServletResponse;
55  
56  
57  /***
58   * Tests {@link DigestProcessingFilter}.
59   *
60   * @author Ben Alex
61   * @version $Id: DigestProcessingFilterTests.java,v 1.10 2005/11/30 00:20:13 benalex Exp $
62   */
63  public class DigestProcessingFilterTests extends TestCase {
64      //~ Constructors ===========================================================
65  
66      public DigestProcessingFilterTests() {
67          super();
68      }
69  
70      public DigestProcessingFilterTests(String arg0) {
71          super(arg0);
72      }
73  
74      //~ Methods ================================================================
75  
76      public static void main(String[] args) {
77          junit.textui.TestRunner.run(DigestProcessingFilterTests.class);
78      }
79  
80      public void testDoFilterWithNonHttpServletRequestDetected()
81          throws Exception {
82          DigestProcessingFilter filter = new DigestProcessingFilter();
83  
84          try {
85              filter.doFilter(null, new MockHttpServletResponse(),
86                  new MockFilterChain());
87              fail("Should have thrown ServletException");
88          } catch (ServletException expected) {
89              assertEquals("Can only process HttpServletRequest",
90                  expected.getMessage());
91          }
92      }
93  
94      public void testDoFilterWithNonHttpServletResponseDetected()
95          throws Exception {
96          DigestProcessingFilter filter = new DigestProcessingFilter();
97  
98          try {
99              filter.doFilter(new MockHttpServletRequest(null, null), null,
100                 new MockFilterChain());
101             fail("Should have thrown ServletException");
102         } catch (ServletException expected) {
103             assertEquals("Can only process HttpServletResponse",
104                 expected.getMessage());
105         }
106     }
107 
108     public void testExpiredNonceReturnsForbiddenWithStaleHeader()
109         throws Exception {
110         Map responseHeaderMap = generateValidHeaders(0);
111 
112         String username = "marissa";
113         String realm = (String) responseHeaderMap.get("realm");
114         String nonce = (String) responseHeaderMap.get("nonce");
115         String uri = "/some_file.html";
116         String qop = (String) responseHeaderMap.get("qop");
117         String nc = "00000002";
118         String cnonce = "c822c727a648aba7";
119         String password = "koala";
120         String responseDigest = DigestProcessingFilter.generateDigest(false,
121                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
122 
123         // Setup our HTTP request
124         MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
125         request.addHeader("Authorization",
126             createAuthorizationHeader(username, realm, nonce, uri,
127                 responseDigest, qop, nc, cnonce));
128         request.setServletPath("/some_file.html");
129 
130         // Launch an application context and access our bean
131         ApplicationContext ctx = new ClassPathXmlApplicationContext(
132                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
133         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
134                 "digestProcessingFilter");
135 
136         // Setup our filter configuration
137         MockFilterConfig config = new MockFilterConfig();
138 
139         // Setup our expectation that the filter chain will be invoked
140         MockFilterChain chain = new MockFilterChain(true);
141         MockHttpServletResponse response = new MockHttpServletResponse();
142 
143         // Test
144         Thread.sleep(1000); // ensures token expired
145         executeFilterInContainerSimulator(config, filter, request, response,
146             chain);
147 
148         assertNull(SecurityContextHolder.getContext().getAuthentication());
149         assertEquals(401, response.getStatus());
150 
151         String header = response.getHeader("WWW-Authenticate").toString()
152                                 .substring(7);
153         String[] headerEntries = StringUtils.commaDelimitedListToStringArray(header);
154         Map headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries,
155                 "=", "\"");
156         assertEquals("true", headerMap.get("stale"));
157     }
158 
159     public void testFilterIgnoresRequestsContainingNoAuthorizationHeader()
160         throws Exception {
161         // Setup our HTTP request
162         MockHttpServletRequest request = new MockHttpServletRequest();
163         request.setServletPath("/some_file.html");
164 
165         // Launch an application context and access our bean
166         ApplicationContext ctx = new ClassPathXmlApplicationContext(
167                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
168         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
169                 "digestProcessingFilter");
170 
171         // Setup our filter configuration
172         MockFilterConfig config = new MockFilterConfig();
173 
174         // Setup our expectation that the filter chain will be invoked
175         MockFilterChain chain = new MockFilterChain(true);
176         MockHttpServletResponse response = new MockHttpServletResponse();
177 
178         // Test
179         executeFilterInContainerSimulator(config, filter, request, response,
180             chain);
181 
182         assertNull(SecurityContextHolder.getContext().getAuthentication());
183     }
184 
185     public void testGettersSetters() {
186         DigestProcessingFilter filter = new DigestProcessingFilter();
187         filter.setUserDetailsService(new MockAuthenticationDao());
188         assertTrue(filter.getUserDetailsService() != null);
189 
190         filter.setAuthenticationEntryPoint(new DigestProcessingFilterEntryPoint());
191         assertTrue(filter.getAuthenticationEntryPoint() != null);
192 
193         filter.setUserCache(null);
194         assertNull(filter.getUserCache());
195         filter.setUserCache(new MockUserCache());
196         assertNotNull(filter.getUserCache());
197     }
198 
199     public void testInvalidDigestAuthorizationTokenGeneratesError()
200         throws Exception {
201         // Setup our HTTP request
202         String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
203 
204         MockHttpServletRequest request = new MockHttpServletRequest();
205         request.addHeader("Authorization",
206             "Digest " + new String(Base64.encodeBase64(token.getBytes())));
207         request.setServletPath("/some_file.html");
208 
209         // Launch an application context and access our bean
210         ApplicationContext ctx = new ClassPathXmlApplicationContext(
211                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
212         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
213                 "digestProcessingFilter");
214 
215         // Setup our filter configuration
216         MockFilterConfig config = new MockFilterConfig();
217 
218         // Setup our expectation that the filter chain will be invoked
219         MockFilterChain chain = new MockFilterChain(false);
220         MockHttpServletResponse response = new MockHttpServletResponse();
221 
222         // Test
223         executeFilterInContainerSimulator(config, filter, request, response,
224             chain);
225         assertEquals(401, response.getStatus());
226 
227         assertNull(SecurityContextHolder.getContext().getAuthentication());
228     }
229 
230     public void testMalformedHeaderReturnsForbidden() throws Exception {
231         // Setup our HTTP request
232         MockHttpServletRequest request = new MockHttpServletRequest();
233         request.addHeader("Authorization", "Digest scsdcsdc");
234         request.setServletPath("/some_file.html");
235 
236         // Launch an application context and access our bean
237         ApplicationContext ctx = new ClassPathXmlApplicationContext(
238                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
239         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
240                 "digestProcessingFilter");
241 
242         // Setup our filter configuration
243         MockFilterConfig config = new MockFilterConfig();
244 
245         // Setup our expectation that the filter chain will be invoked
246         MockFilterChain chain = new MockFilterChain(true);
247         MockHttpServletResponse response = new MockHttpServletResponse();
248 
249         // Test
250         executeFilterInContainerSimulator(config, filter, request, response,
251             chain);
252 
253         assertNull(SecurityContextHolder.getContext().getAuthentication());
254         assertEquals(401, response.getStatus());
255     }
256 
257     public void testNonBase64EncodedNonceReturnsForbidden()
258         throws Exception {
259         Map responseHeaderMap = generateValidHeaders(60);
260 
261         String username = "marissa";
262         String realm = (String) responseHeaderMap.get("realm");
263         String nonce = "NOT_BASE_64_ENCODED";
264         String uri = "/some_file.html";
265         String qop = (String) responseHeaderMap.get("qop");
266         String nc = "00000002";
267         String cnonce = "c822c727a648aba7";
268         String password = "koala";
269         String responseDigest = DigestProcessingFilter.generateDigest(false,
270                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
271 
272         // Setup our HTTP request
273         MockHttpServletRequest request = new MockHttpServletRequest();
274         request.addHeader("Authorization",
275             createAuthorizationHeader(username, realm, nonce, uri,
276                 responseDigest, qop, nc, cnonce));
277         request.setServletPath("/some_file.html");
278 
279         // Launch an application context and access our bean
280         ApplicationContext ctx = new ClassPathXmlApplicationContext(
281                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
282         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
283                 "digestProcessingFilter");
284 
285         // Setup our filter configuration
286         MockFilterConfig config = new MockFilterConfig();
287 
288         // Setup our expectation that the filter chain will be invoked
289         MockFilterChain chain = new MockFilterChain(true);
290         MockHttpServletResponse response = new MockHttpServletResponse();
291 
292         // Test
293         executeFilterInContainerSimulator(config, filter, request, response,
294             chain);
295 
296         assertNull(SecurityContextHolder.getContext().getAuthentication());
297         assertEquals(401, response.getStatus());
298     }
299 
300     public void testNonceWithIncorrectSignatureForNumericFieldReturnsForbidden()
301         throws Exception {
302         Map responseHeaderMap = generateValidHeaders(60);
303 
304         String username = "marissa";
305         String realm = (String) responseHeaderMap.get("realm");
306         String nonce = new String(Base64.encodeBase64(
307                     "123456:incorrectStringPassword".getBytes()));
308         String uri = "/some_file.html";
309         String qop = (String) responseHeaderMap.get("qop");
310         String nc = "00000002";
311         String cnonce = "c822c727a648aba7";
312         String password = "koala";
313         String responseDigest = DigestProcessingFilter.generateDigest(false,
314                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
315 
316         // Setup our HTTP request
317         MockHttpServletRequest request = new MockHttpServletRequest();
318         request.addHeader("Authorization",
319             createAuthorizationHeader(username, realm, nonce, uri,
320                 responseDigest, qop, nc, cnonce));
321         request.setServletPath("/some_file.html");
322 
323         // Launch an application context and access our bean
324         ApplicationContext ctx = new ClassPathXmlApplicationContext(
325                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
326         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
327                 "digestProcessingFilter");
328 
329         // Setup our filter configuration
330         MockFilterConfig config = new MockFilterConfig();
331 
332         // Setup our expectation that the filter chain will be invoked
333         MockFilterChain chain = new MockFilterChain(false);
334         MockHttpServletResponse response = new MockHttpServletResponse();
335 
336         // Test
337         executeFilterInContainerSimulator(config, filter, request, response,
338             chain);
339 
340         assertNull(SecurityContextHolder.getContext().getAuthentication());
341         assertEquals(401, response.getStatus());
342     }
343 
344     public void testNonceWithNonNumericFirstElementReturnsForbidden()
345         throws Exception {
346         Map responseHeaderMap = generateValidHeaders(60);
347 
348         String username = "marissa";
349         String realm = (String) responseHeaderMap.get("realm");
350         String nonce = new String(Base64.encodeBase64(
351                     "hello:ignoredSecondElement".getBytes()));
352         String uri = "/some_file.html";
353         String qop = (String) responseHeaderMap.get("qop");
354         String nc = "00000002";
355         String cnonce = "c822c727a648aba7";
356         String password = "koala";
357         String responseDigest = DigestProcessingFilter.generateDigest(false,
358                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
359 
360         // Setup our HTTP request
361         MockHttpServletRequest request = new MockHttpServletRequest();
362         request.addHeader("Authorization",
363             createAuthorizationHeader(username, realm, nonce, uri,
364                 responseDigest, qop, nc, cnonce));
365         request.setServletPath("/some_file.html");
366 
367         // Launch an application context and access our bean
368         ApplicationContext ctx = new ClassPathXmlApplicationContext(
369                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
370         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
371                 "digestProcessingFilter");
372 
373         // Setup our filter configuration
374         MockFilterConfig config = new MockFilterConfig();
375 
376         // Setup our expectation that the filter chain will be invoked
377         MockFilterChain chain = new MockFilterChain(true);
378         MockHttpServletResponse response = new MockHttpServletResponse();
379 
380         // Test
381         executeFilterInContainerSimulator(config, filter, request, response,
382             chain);
383 
384         assertNull(SecurityContextHolder.getContext().getAuthentication());
385         assertEquals(401, response.getStatus());
386     }
387 
388     public void testNonceWithoutTwoColonSeparatedElementsReturnsForbidden()
389         throws Exception {
390         Map responseHeaderMap = generateValidHeaders(60);
391 
392         String username = "marissa";
393         String realm = (String) responseHeaderMap.get("realm");
394         String nonce = new String(Base64.encodeBase64(
395                     "a base 64 string without a colon".getBytes()));
396         String uri = "/some_file.html";
397         String qop = (String) responseHeaderMap.get("qop");
398         String nc = "00000002";
399         String cnonce = "c822c727a648aba7";
400         String password = "koala";
401         String responseDigest = DigestProcessingFilter.generateDigest(false,
402                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
403 
404         // Setup our HTTP request
405         MockHttpServletRequest request = new MockHttpServletRequest();
406         request.addHeader("Authorization",
407             createAuthorizationHeader(username, realm, nonce, uri,
408                 responseDigest, qop, nc, cnonce));
409         request.setServletPath("/some_file.html");
410 
411         // Launch an application context and access our bean
412         ApplicationContext ctx = new ClassPathXmlApplicationContext(
413                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
414         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
415                 "digestProcessingFilter");
416 
417         // Setup our filter configuration
418         MockFilterConfig config = new MockFilterConfig();
419 
420         // Setup our expectation that the filter chain will be invoked
421         MockFilterChain chain = new MockFilterChain(true);
422         MockHttpServletResponse response = new MockHttpServletResponse();
423 
424         // Test
425         executeFilterInContainerSimulator(config, filter, request, response,
426             chain);
427 
428         assertNull(SecurityContextHolder.getContext().getAuthentication());
429         assertEquals(401, response.getStatus());
430     }
431 
432     public void testNormalOperationWhenPasswordIsAlreadyEncoded()
433         throws Exception {
434         Map responseHeaderMap = generateValidHeaders(60);
435 
436         String username = "marissa";
437         String realm = (String) responseHeaderMap.get("realm");
438         String nonce = (String) responseHeaderMap.get("nonce");
439         String uri = "/some_file.html";
440         String qop = (String) responseHeaderMap.get("qop");
441         String nc = "00000002";
442         String cnonce = "c822c727a648aba7";
443         String password = DigestProcessingFilter.encodePasswordInA1Format(username,
444                 realm, "koala");
445         String responseDigest = DigestProcessingFilter.generateDigest(true,
446                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
447 
448         // Setup our HTTP request
449         MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
450         request.addHeader("Authorization",
451             createAuthorizationHeader(username, realm, nonce, uri,
452                 responseDigest, qop, nc, cnonce));
453         request.setServletPath("/some_file.html");
454 
455         // Launch an application context and access our bean
456         ApplicationContext ctx = new ClassPathXmlApplicationContext(
457                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
458         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
459                 "digestProcessingFilter");
460 
461         // Setup our filter configuration
462         MockFilterConfig config = new MockFilterConfig();
463 
464         // Setup our expectation that the filter chain will be invoked
465         MockFilterChain chain = new MockFilterChain(true);
466         MockHttpServletResponse response = new MockHttpServletResponse();
467 
468         // Test
469         executeFilterInContainerSimulator(config, filter, request, response,
470             chain);
471 
472         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
473         assertEquals("marissa",
474             ((UserDetails) SecurityContextHolder.getContext().getAuthentication()
475                                                 .getPrincipal()).getUsername());
476     }
477 
478     public void testNormalOperationWhenPasswordNotAlreadyEncoded()
479         throws Exception {
480         Map responseHeaderMap = generateValidHeaders(60);
481 
482         String username = "marissa";
483         String realm = (String) responseHeaderMap.get("realm");
484         String nonce = (String) responseHeaderMap.get("nonce");
485         String uri = "/some_file.html";
486         String qop = (String) responseHeaderMap.get("qop");
487         String nc = "00000002";
488         String cnonce = "c822c727a648aba7";
489         String password = "koala";
490         String responseDigest = DigestProcessingFilter.generateDigest(false,
491                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
492 
493         // Setup our HTTP request
494         MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
495         request.addHeader("Authorization",
496             createAuthorizationHeader(username, realm, nonce, uri,
497                 responseDigest, qop, nc, cnonce));
498         request.setServletPath("/some_file.html");
499 
500         // Launch an application context and access our bean
501         ApplicationContext ctx = new ClassPathXmlApplicationContext(
502                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
503         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
504                 "digestProcessingFilter");
505 
506         // Setup our filter configuration
507         MockFilterConfig config = new MockFilterConfig();
508 
509         // Setup our expectation that the filter chain will be invoked
510         MockFilterChain chain = new MockFilterChain(true);
511         MockHttpServletResponse response = new MockHttpServletResponse();
512 
513         // Test
514         executeFilterInContainerSimulator(config, filter, request, response,
515             chain);
516 
517         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
518         assertEquals("marissa",
519             ((UserDetails) SecurityContextHolder.getContext().getAuthentication()
520                                                 .getPrincipal()).getUsername());
521     }
522 
523     public void testOtherAuthorizationSchemeIsIgnored()
524         throws Exception {
525         // Setup our HTTP request
526         MockHttpServletRequest request = new MockHttpServletRequest();
527         request.addHeader("Authorization", "SOME_OTHER_AUTHENTICATION_SCHEME");
528         request.setServletPath("/some_file.html");
529 
530         // Launch an application context and access our bean
531         ApplicationContext ctx = new ClassPathXmlApplicationContext(
532                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
533         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
534                 "digestProcessingFilter");
535 
536         // Setup our filter configuration
537         MockFilterConfig config = new MockFilterConfig();
538 
539         // Setup our expectation that the filter chain will be invoked
540         MockFilterChain chain = new MockFilterChain(true);
541         MockHttpServletResponse response = new MockHttpServletResponse();
542 
543         // Test
544         executeFilterInContainerSimulator(config, filter, request, response,
545             chain);
546 
547         assertNull(SecurityContextHolder.getContext().getAuthentication());
548     }
549 
550     public void testStartupDetectsMissingAuthenticationDao()
551         throws Exception {
552         try {
553             DigestProcessingFilter filter = new DigestProcessingFilter();
554             filter.setAuthenticationEntryPoint(new DigestProcessingFilterEntryPoint());
555             filter.afterPropertiesSet();
556             fail("Should have thrown IllegalArgumentException");
557         } catch (IllegalArgumentException expected) {
558             assertEquals("An AuthenticationDao is required",
559                 expected.getMessage());
560         }
561     }
562 
563     public void testStartupDetectsMissingAuthenticationEntryPoint()
564         throws Exception {
565         try {
566             DigestProcessingFilter filter = new DigestProcessingFilter();
567             filter.setUserDetailsService(new MockAuthenticationDao());
568             filter.afterPropertiesSet();
569             fail("Should have thrown IllegalArgumentException");
570         } catch (IllegalArgumentException expected) {
571             assertEquals("A DigestProcessingFilterEntryPoint is required",
572                 expected.getMessage());
573         }
574     }
575 
576     public void testSuccessLoginThenFailureLoginResultsInSessionLoosingToken()
577         throws Exception {
578         Map responseHeaderMap = generateValidHeaders(60);
579 
580         String username = "marissa";
581         String realm = (String) responseHeaderMap.get("realm");
582         String nonce = (String) responseHeaderMap.get("nonce");
583         String uri = "/some_file.html";
584         String qop = (String) responseHeaderMap.get("qop");
585         String nc = "00000002";
586         String cnonce = "c822c727a648aba7";
587         String password = "koala";
588         String responseDigest = DigestProcessingFilter.generateDigest(false,
589                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
590 
591         // Setup our HTTP request
592         MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
593         request.addHeader("Authorization",
594             createAuthorizationHeader(username, realm, nonce, uri,
595                 responseDigest, qop, nc, cnonce));
596         request.setServletPath("/some_file.html");
597 
598         // Launch an application context and access our bean
599         ApplicationContext ctx = new ClassPathXmlApplicationContext(
600                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
601         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
602                 "digestProcessingFilter");
603 
604         // Setup our filter configuration
605         MockFilterConfig config = new MockFilterConfig();
606 
607         // Setup our expectation that the filter chain will be invoked
608         MockFilterChain chain = new MockFilterChain(true);
609         MockHttpServletResponse response = new MockHttpServletResponse();
610 
611         // Test
612         executeFilterInContainerSimulator(config, filter, request, response,
613             chain);
614 
615         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
616 
617         // Now retry, giving an invalid nonce
618         password = "WRONG_PASSWORD";
619         responseDigest = DigestProcessingFilter.generateDigest(false, username,
620                 realm, password, "GET", uri, qop, nonce, nc, cnonce);
621 
622         request = new MockHttpServletRequest();
623         request.addHeader("Authorization",
624             createAuthorizationHeader(username, realm, nonce, uri,
625                 responseDigest, qop, nc, cnonce));
626         executeFilterInContainerSimulator(config, filter, request, response,
627             chain);
628 
629         // Check we lost our previous authentication
630         assertNull(SecurityContextHolder.getContext().getAuthentication());
631         assertEquals(401, response.getStatus());
632     }
633 
634     public void testWrongCnonceBasedOnDigestReturnsForbidden()
635         throws Exception {
636         Map responseHeaderMap = generateValidHeaders(60);
637 
638         String username = "marissa";
639         String realm = (String) responseHeaderMap.get("realm");
640         String nonce = (String) responseHeaderMap.get("nonce");
641         String uri = "/some_file.html";
642         String qop = (String) responseHeaderMap.get("qop");
643         String nc = "00000002";
644         String cnonce = "NOT_SAME_AS_USED_FOR_DIGEST_COMPUTATION";
645         String password = "koala";
646         String responseDigest = DigestProcessingFilter.generateDigest(false,
647                 username, realm, password, "GET", uri, qop, nonce, nc,
648                 "DIFFERENT_CNONCE");
649 
650         // Setup our HTTP request
651         MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
652         request.addHeader("Authorization",
653             createAuthorizationHeader(username, realm, nonce, uri,
654                 responseDigest, qop, nc, cnonce));
655         request.setServletPath("/some_file.html");
656 
657         // Launch an application context and access our bean
658         ApplicationContext ctx = new ClassPathXmlApplicationContext(
659                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
660         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
661                 "digestProcessingFilter");
662 
663         // Setup our filter configuration
664         MockFilterConfig config = new MockFilterConfig();
665 
666         // Setup our expectation that the filter chain will be invoked
667         MockFilterChain chain = new MockFilterChain(true);
668         MockHttpServletResponse response = new MockHttpServletResponse();
669 
670         // Test
671         executeFilterInContainerSimulator(config, filter, request, response,
672             chain);
673 
674         assertNull(SecurityContextHolder.getContext().getAuthentication());
675         assertEquals(401, response.getStatus());
676     }
677 
678     public void testWrongDigestReturnsForbidden() throws Exception {
679         Map responseHeaderMap = generateValidHeaders(60);
680 
681         String username = "marissa";
682         String realm = (String) responseHeaderMap.get("realm");
683         String nonce = (String) responseHeaderMap.get("nonce");
684         String uri = "/some_file.html";
685         String qop = (String) responseHeaderMap.get("qop");
686         String nc = "00000002";
687         String cnonce = "c822c727a648aba7";
688         String password = "WRONG_PASSWORD";
689         String responseDigest = DigestProcessingFilter.generateDigest(false,
690                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
691 
692         // Setup our HTTP request
693         MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
694         request.addHeader("Authorization",
695             createAuthorizationHeader(username, realm, nonce, uri,
696                 responseDigest, qop, nc, cnonce));
697         request.setServletPath("/some_file.html");
698 
699         // Launch an application context and access our bean
700         ApplicationContext ctx = new ClassPathXmlApplicationContext(
701                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
702         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
703                 "digestProcessingFilter");
704 
705         // Setup our filter configuration
706         MockFilterConfig config = new MockFilterConfig();
707 
708         // Setup our expectation that the filter chain will be invoked
709         MockFilterChain chain = new MockFilterChain(true);
710         MockHttpServletResponse response = new MockHttpServletResponse();
711 
712         // Test
713         executeFilterInContainerSimulator(config, filter, request, response,
714             chain);
715 
716         assertNull(SecurityContextHolder.getContext().getAuthentication());
717         assertEquals(401, response.getStatus());
718     }
719 
720     public void testWrongRealmReturnsForbidden() throws Exception {
721         Map responseHeaderMap = generateValidHeaders(60);
722 
723         String username = "marissa";
724         String realm = "WRONG_REALM";
725         String nonce = (String) responseHeaderMap.get("nonce");
726         String uri = "/some_file.html";
727         String qop = (String) responseHeaderMap.get("qop");
728         String nc = "00000002";
729         String cnonce = "c822c727a648aba7";
730         String password = "koala";
731         String responseDigest = DigestProcessingFilter.generateDigest(false,
732                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
733 
734         // Setup our HTTP request
735         MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
736         request.addHeader("Authorization",
737             createAuthorizationHeader(username, realm, nonce, uri,
738                 responseDigest, qop, nc, cnonce));
739         request.setServletPath("/some_file.html");
740 
741         // Launch an application context and access our bean
742         ApplicationContext ctx = new ClassPathXmlApplicationContext(
743                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
744         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
745                 "digestProcessingFilter");
746 
747         // Setup our filter configuration
748         MockFilterConfig config = new MockFilterConfig();
749 
750         // Setup our expectation that the filter chain will be invoked
751         MockFilterChain chain = new MockFilterChain(true);
752         MockHttpServletResponse response = new MockHttpServletResponse();
753 
754         // Test
755         executeFilterInContainerSimulator(config, filter, request, response,
756             chain);
757 
758         assertNull(SecurityContextHolder.getContext().getAuthentication());
759         assertEquals(401, response.getStatus());
760     }
761 
762     public void testWrongUsernameReturnsForbidden() throws Exception {
763         Map responseHeaderMap = generateValidHeaders(60);
764 
765         String username = "NOT_A_KNOWN_USER";
766         String realm = (String) responseHeaderMap.get("realm");
767         String nonce = (String) responseHeaderMap.get("nonce");
768         String uri = "/some_file.html";
769         String qop = (String) responseHeaderMap.get("qop");
770         String nc = "00000002";
771         String cnonce = "c822c727a648aba7";
772         String password = "koala";
773         String responseDigest = DigestProcessingFilter.generateDigest(false,
774                 username, realm, password, "GET", uri, qop, nonce, nc, cnonce);
775 
776         // Setup our HTTP request
777         MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
778         request.addHeader("Authorization",
779             createAuthorizationHeader(username, realm, nonce, uri,
780                 responseDigest, qop, nc, cnonce));
781         request.setServletPath("/some_file.html");
782 
783         // Launch an application context and access our bean
784         ApplicationContext ctx = new ClassPathXmlApplicationContext(
785                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
786         DigestProcessingFilter filter = (DigestProcessingFilter) ctx.getBean(
787                 "digestProcessingFilter");
788 
789         // Setup our filter configuration
790         MockFilterConfig config = new MockFilterConfig();
791 
792         // Setup our expectation that the filter chain will be invoked
793         MockFilterChain chain = new MockFilterChain(true);
794         MockHttpServletResponse response = new MockHttpServletResponse();
795 
796         // Test
797         executeFilterInContainerSimulator(config, filter, request, response,
798             chain);
799 
800         assertNull(SecurityContextHolder.getContext().getAuthentication());
801         assertEquals(401, response.getStatus());
802     }
803 
804     protected void setUp() throws Exception {
805         super.setUp();
806         SecurityContextHolder.setContext(new SecurityContextImpl());
807     }
808 
809     protected void tearDown() throws Exception {
810         super.tearDown();
811         SecurityContextHolder.setContext(new SecurityContextImpl());
812     }
813 
814     private String createAuthorizationHeader(String username, String realm,
815         String nonce, String uri, String responseDigest, String qop, String nc,
816         String cnonce) {
817         return "Digest username=\"" + username + "\", realm=\"" + realm
818         + "\", nonce=\"" + nonce + "\", uri=\"" + uri + "\", response=\""
819         + responseDigest + "\", qop=" + qop + ", nc=" + nc + ", cnonce=\""
820         + cnonce + "\"";
821     }
822 
823     private void executeFilterInContainerSimulator(FilterConfig filterConfig,
824         Filter filter, ServletRequest request, ServletResponse response,
825         FilterChain filterChain) throws ServletException, IOException {
826         filter.init(filterConfig);
827         filter.doFilter(request, response, filterChain);
828         filter.destroy();
829     }
830 
831     private Map generateValidHeaders(int nonceValidityPeriod)
832         throws Exception {
833         ApplicationContext ctx = new ClassPathXmlApplicationContext(
834                 "org/acegisecurity/ui/digestauth/filtertest-valid.xml");
835         DigestProcessingFilterEntryPoint ep = (DigestProcessingFilterEntryPoint) ctx
836             .getBean("digestProcessingFilterEntryPoint");
837         ep.setNonceValiditySeconds(nonceValidityPeriod);
838 
839         MockHttpServletRequest request = new MockHttpServletRequest();
840         request.setRequestURI("/some_path");
841 
842         MockHttpServletResponse response = new MockHttpServletResponse();
843 
844         ep.commence(request, response, new DisabledException("foobar"));
845 
846         // Break up response header
847         String header = response.getHeader("WWW-Authenticate").toString()
848                                 .substring(7);
849         String[] headerEntries = StringUtils.commaDelimitedListToStringArray(header);
850         Map headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries,
851                 "=", "\"");
852 
853         return headerMap;
854     }
855 
856     //~ Inner Classes ==========================================================
857 
858     private class MockAuthenticationDao implements UserDetailsService {
859         public UserDetails loadUserByUsername(String username)
860             throws UsernameNotFoundException, DataAccessException {
861             return null;
862         }
863     }
864 
865     private class MockFilterChain implements FilterChain {
866         private boolean expectToProceed;
867 
868         public MockFilterChain(boolean expectToProceed) {
869             this.expectToProceed = expectToProceed;
870         }
871 
872         private MockFilterChain() {
873             super();
874         }
875 
876         public void doFilter(ServletRequest request, ServletResponse response)
877             throws IOException, ServletException {
878             if (expectToProceed) {
879                 assertTrue(true);
880             } else {
881                 fail("Did not expect filter chain to proceed");
882             }
883         }
884     }
885 
886     private class MockUserCache implements UserCache {
887         public UserDetails getUserFromCache(String username) {
888             return null;
889         }
890 
891         public void putUserInCache(UserDetails user) {}
892 
893         public void removeUserFromCache(String username) {}
894     }
895 }