1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.acegisecurity.ui.digestauth;
17
18 import org.acegisecurity.MockFilterChain;
19 import org.acegisecurity.MockFilterConfig;
20
21 import org.acegisecurity.context.SecurityContextHolder;
22
23 import org.acegisecurity.providers.dao.cache.NullUserCache;
24
25 import org.acegisecurity.userdetails.UserDetails;
26 import org.acegisecurity.userdetails.memory.InMemoryDaoImpl;
27 import org.acegisecurity.userdetails.memory.UserMap;
28 import org.acegisecurity.userdetails.memory.UserMapEditor;
29
30 import org.acegisecurity.util.StringSplitUtils;
31
32 import org.apache.commons.codec.binary.Base64;
33 import org.apache.commons.codec.digest.DigestUtils;
34
35 import org.jmock.Mock;
36 import org.jmock.MockObjectTestCase;
37
38 import org.springframework.mock.web.MockHttpServletRequest;
39 import org.springframework.mock.web.MockHttpServletResponse;
40
41 import org.springframework.util.StringUtils;
42
43 import java.io.IOException;
44
45 import java.util.Map;
46
47 import javax.servlet.Filter;
48 import javax.servlet.FilterChain;
49 import javax.servlet.ServletException;
50 import javax.servlet.ServletRequest;
51
52
53
54
55
56
57
58
59
60 public class DigestProcessingFilterTests extends MockObjectTestCase {
61
62
63 private static final String NC = "00000002";
64 private static final String CNONCE = "c822c727a648aba7";
65 private static final String REALM = "The Actual, Correct Realm Name";
66 private static final String KEY = "acegi";
67 private static final String QOP = "auth";
68 private static final String USERNAME = "marissa,ok";
69 private static final String PASSWORD = "koala";
70 private static final String REQUEST_URI = "/some_file.html";
71
72
73
74
75 private static final String NONCE = generateNonce(60);
76
77
78
79
80 private DigestProcessingFilter filter;
81 private MockHttpServletRequest request;
82
83
84
85 public DigestProcessingFilterTests() {
86 }
87
88 public DigestProcessingFilterTests(String arg0) {
89 super(arg0);
90 }
91
92
93
94 private String createAuthorizationHeader(String username, String realm, String nonce, String uri,
95 String responseDigest, String qop, String nc, String cnonce) {
96 return "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", uri=\"" + uri
97 + "\", response=\"" + responseDigest + "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\"";
98 }
99
100 private MockHttpServletResponse executeFilterInContainerSimulator(Filter filter, ServletRequest request,
101 boolean expectChainToProceed) throws ServletException, IOException {
102 filter.init(new MockFilterConfig());
103
104 MockHttpServletResponse response = new MockHttpServletResponse();
105 Mock mockChain = mock(FilterChain.class);
106 FilterChain chain = (FilterChain) mockChain.proxy();
107
108 mockChain.expects(expectChainToProceed ? once() : never()).method("doFilter");
109
110 filter.doFilter(request, response, chain);
111 filter.destroy();
112
113 return response;
114 }
115
116 private static String generateNonce(int validitySeconds) {
117 long expiryTime = System.currentTimeMillis() + (validitySeconds * 1000);
118 String signatureValue = new String(DigestUtils.md5Hex(expiryTime + ":" + KEY));
119 String nonceValue = expiryTime + ":" + signatureValue;
120
121 return new String(Base64.encodeBase64(nonceValue.getBytes()));
122 }
123
124 protected void setUp() throws Exception {
125 super.setUp();
126 SecurityContextHolder.clearContext();
127
128
129 InMemoryDaoImpl dao = new InMemoryDaoImpl();
130 UserMapEditor editor = new UserMapEditor();
131 editor.setAsText("marissa,ok=koala,ROLE_ONE,ROLE_TWO,enabled\r\n");
132 dao.setUserMap((UserMap) editor.getValue());
133
134 DigestProcessingFilterEntryPoint ep = new DigestProcessingFilterEntryPoint();
135 ep.setRealmName(REALM);
136 ep.setKey(KEY);
137
138 filter = new DigestProcessingFilter();
139 filter.setUserDetailsService(dao);
140 filter.setAuthenticationEntryPoint(ep);
141
142 request = new MockHttpServletRequest("GET", REQUEST_URI);
143 request.setServletPath(REQUEST_URI);
144 }
145
146 protected void tearDown() throws Exception {
147 super.tearDown();
148 SecurityContextHolder.clearContext();
149 }
150
151 public void testDoFilterWithNonHttpServletRequestDetected()
152 throws Exception {
153 DigestProcessingFilter filter = new DigestProcessingFilter();
154
155 try {
156 filter.doFilter(null, new MockHttpServletResponse(), new MockFilterChain());
157 fail("Should have thrown ServletException");
158 } catch (ServletException expected) {
159 assertEquals("Can only process HttpServletRequest", expected.getMessage());
160 }
161 }
162
163 public void testDoFilterWithNonHttpServletResponseDetected()
164 throws Exception {
165 DigestProcessingFilter filter = new DigestProcessingFilter();
166
167 try {
168 filter.doFilter(new MockHttpServletRequest(null, null), null, new MockFilterChain());
169 fail("Should have thrown ServletException");
170 } catch (ServletException expected) {
171 assertEquals("Can only process HttpServletResponse", expected.getMessage());
172 }
173 }
174
175 public void testExpiredNonceReturnsForbiddenWithStaleHeader()
176 throws Exception {
177 String nonce = generateNonce(0);
178 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
179 REQUEST_URI, QOP, nonce, NC, CNONCE);
180
181 request.addHeader("Authorization",
182 createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
183
184 Thread.sleep(1000);
185
186 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
187
188 assertNull(SecurityContextHolder.getContext().getAuthentication());
189 assertEquals(401, response.getStatus());
190
191 String header = response.getHeader("WWW-Authenticate").toString().substring(7);
192 String[] headerEntries = StringUtils.commaDelimitedListToStringArray(header);
193 Map headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
194 assertEquals("true", headerMap.get("stale"));
195 }
196
197 public void testFilterIgnoresRequestsContainingNoAuthorizationHeader()
198 throws Exception {
199 executeFilterInContainerSimulator(filter, request, true);
200
201 assertNull(SecurityContextHolder.getContext().getAuthentication());
202 }
203
204 public void testGettersSetters() {
205 DigestProcessingFilter filter = new DigestProcessingFilter();
206 filter.setUserDetailsService(new InMemoryDaoImpl());
207 assertTrue(filter.getUserDetailsService() != null);
208
209 filter.setAuthenticationEntryPoint(new DigestProcessingFilterEntryPoint());
210 assertTrue(filter.getAuthenticationEntryPoint() != null);
211
212 filter.setUserCache(null);
213 assertNull(filter.getUserCache());
214 filter.setUserCache(new NullUserCache());
215 assertNotNull(filter.getUserCache());
216 }
217
218 public void testInvalidDigestAuthorizationTokenGeneratesError()
219 throws Exception {
220 String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
221
222 request.addHeader("Authorization", "Digest " + new String(Base64.encodeBase64(token.getBytes())));
223
224 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
225
226 assertEquals(401, response.getStatus());
227 assertNull(SecurityContextHolder.getContext().getAuthentication());
228 }
229
230 public void testMalformedHeaderReturnsForbidden() throws Exception {
231 request.addHeader("Authorization", "Digest scsdcsdc");
232
233 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
234
235 assertNull(SecurityContextHolder.getContext().getAuthentication());
236 assertEquals(401, response.getStatus());
237 }
238
239 public void testNonBase64EncodedNonceReturnsForbidden()
240 throws Exception {
241 String nonce = "NOT_BASE_64_ENCODED";
242
243 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
244 REQUEST_URI, QOP, nonce, NC, CNONCE);
245
246 request.addHeader("Authorization",
247 createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
248
249 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
250
251 assertNull(SecurityContextHolder.getContext().getAuthentication());
252 assertEquals(401, response.getStatus());
253 }
254
255 public void testNonceWithIncorrectSignatureForNumericFieldReturnsForbidden()
256 throws Exception {
257 String nonce = new String(Base64.encodeBase64("123456:incorrectStringPassword".getBytes()));
258 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
259 REQUEST_URI, QOP, nonce, NC, CNONCE);
260
261 request.addHeader("Authorization",
262 createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
263
264 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
265
266 assertNull(SecurityContextHolder.getContext().getAuthentication());
267 assertEquals(401, response.getStatus());
268 }
269
270 public void testNonceWithNonNumericFirstElementReturnsForbidden()
271 throws Exception {
272 String nonce = new String(Base64.encodeBase64("hello:ignoredSecondElement".getBytes()));
273 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
274 REQUEST_URI, QOP, nonce, NC, CNONCE);
275
276 request.addHeader("Authorization",
277 createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
278
279 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
280
281 assertNull(SecurityContextHolder.getContext().getAuthentication());
282 assertEquals(401, response.getStatus());
283 }
284
285 public void testNonceWithoutTwoColonSeparatedElementsReturnsForbidden()
286 throws Exception {
287 String nonce = new String(Base64.encodeBase64("a base 64 string without a colon".getBytes()));
288 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
289 REQUEST_URI, QOP, nonce, NC, CNONCE);
290
291 request.addHeader("Authorization",
292 createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
293
294 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
295
296 assertNull(SecurityContextHolder.getContext().getAuthentication());
297 assertEquals(401, response.getStatus());
298 }
299
300 public void testNormalOperationWhenPasswordIsAlreadyEncoded()
301 throws Exception {
302 String encodedPassword = DigestProcessingFilter.encodePasswordInA1Format(USERNAME, REALM, PASSWORD);
303 String responseDigest = DigestProcessingFilter.generateDigest(true, USERNAME, REALM, encodedPassword, "GET",
304 REQUEST_URI, QOP, NONCE, NC, CNONCE);
305
306 request.addHeader("Authorization",
307 createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
308
309 executeFilterInContainerSimulator(filter, request, true);
310
311 assertNotNull(SecurityContextHolder.getContext().getAuthentication());
312 assertEquals(USERNAME,
313 ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
314 }
315
316 public void testNormalOperationWhenPasswordNotAlreadyEncoded()
317 throws Exception {
318 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
319 REQUEST_URI, QOP, NONCE, NC, CNONCE);
320
321 request.addHeader("Authorization",
322 createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
323
324 executeFilterInContainerSimulator(filter, request, true);
325
326 assertNotNull(SecurityContextHolder.getContext().getAuthentication());
327 assertEquals(USERNAME,
328 ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
329 }
330
331 public void testOtherAuthorizationSchemeIsIgnored()
332 throws Exception {
333 request.addHeader("Authorization", "SOME_OTHER_AUTHENTICATION_SCHEME");
334
335 executeFilterInContainerSimulator(filter, request, true);
336
337 assertNull(SecurityContextHolder.getContext().getAuthentication());
338 }
339
340 public void testStartupDetectsMissingAuthenticationEntryPoint()
341 throws Exception {
342 try {
343 DigestProcessingFilter filter = new DigestProcessingFilter();
344 filter.setUserDetailsService(new InMemoryDaoImpl());
345 filter.afterPropertiesSet();
346 fail("Should have thrown IllegalArgumentException");
347 } catch (IllegalArgumentException expected) {
348 assertEquals("A DigestProcessingFilterEntryPoint is required", expected.getMessage());
349 }
350 }
351
352 public void testStartupDetectsMissingUserDetailsService()
353 throws Exception {
354 try {
355 DigestProcessingFilter filter = new DigestProcessingFilter();
356 filter.setAuthenticationEntryPoint(new DigestProcessingFilterEntryPoint());
357 filter.afterPropertiesSet();
358 fail("Should have thrown IllegalArgumentException");
359 } catch (IllegalArgumentException expected) {
360 assertEquals("A UserDetailsService is required", expected.getMessage());
361 }
362 }
363
364 public void testSuccessLoginThenFailureLoginResultsInSessionLosingToken()
365 throws Exception {
366 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
367 REQUEST_URI, QOP, NONCE, NC, CNONCE);
368
369 request.addHeader("Authorization",
370 createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
371
372 executeFilterInContainerSimulator(filter, request, true);
373
374 assertNotNull(SecurityContextHolder.getContext().getAuthentication());
375
376
377 responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, "WRONG_PASSWORD", "GET",
378 REQUEST_URI, QOP, NONCE, NC, CNONCE);
379
380 request = new MockHttpServletRequest();
381 request.addHeader("Authorization",
382 createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
383
384 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
385
386
387 assertNull(SecurityContextHolder.getContext().getAuthentication());
388 assertEquals(401, response.getStatus());
389 }
390
391 public void testWrongCnonceBasedOnDigestReturnsForbidden()
392 throws Exception {
393 String cnonce = "NOT_SAME_AS_USED_FOR_DIGEST_COMPUTATION";
394
395 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
396 REQUEST_URI, QOP, NONCE, NC, "DIFFERENT_CNONCE");
397
398 request.addHeader("Authorization",
399 createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, cnonce));
400
401 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
402
403 assertNull(SecurityContextHolder.getContext().getAuthentication());
404 assertEquals(401, response.getStatus());
405 }
406
407 public void testWrongDigestReturnsForbidden() throws Exception {
408 String password = "WRONG_PASSWORD";
409 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, REALM, password, "GET",
410 REQUEST_URI, QOP, NONCE, NC, CNONCE);
411
412 request.addHeader("Authorization",
413 createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
414
415 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
416
417 assertNull(SecurityContextHolder.getContext().getAuthentication());
418 assertEquals(401, response.getStatus());
419 }
420
421 public void testWrongRealmReturnsForbidden() throws Exception {
422 String realm = "WRONG_REALM";
423 String responseDigest = DigestProcessingFilter.generateDigest(false, USERNAME, realm, PASSWORD, "GET",
424 REQUEST_URI, QOP, NONCE, NC, CNONCE);
425
426 request.addHeader("Authorization",
427 createAuthorizationHeader(USERNAME, realm, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
428
429 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
430
431 assertNull(SecurityContextHolder.getContext().getAuthentication());
432 assertEquals(401, response.getStatus());
433 }
434
435 public void testWrongUsernameReturnsForbidden() throws Exception {
436 String responseDigest = DigestProcessingFilter.generateDigest(false, "NOT_A_KNOWN_USER", REALM, PASSWORD,
437 "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
438
439 request.addHeader("Authorization",
440 createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
441
442 MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
443
444 assertNull(SecurityContextHolder.getContext().getAuthentication());
445 assertEquals(401, response.getStatus());
446 }
447 }