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.providers.cas;
17  
18  import java.util.HashMap;
19  import java.util.List;
20  import java.util.Map;
21  import java.util.Vector;
22  
23  import junit.framework.TestCase;
24  
25  import org.acegisecurity.Authentication;
26  import org.acegisecurity.AuthenticationException;
27  import org.acegisecurity.BadCredentialsException;
28  import org.acegisecurity.GrantedAuthority;
29  import org.acegisecurity.GrantedAuthorityImpl;
30  import org.acegisecurity.providers.TestingAuthenticationToken;
31  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
32  import org.acegisecurity.providers.cas.ticketvalidator.AbstractTicketValidator;
33  import org.acegisecurity.ui.cas.CasProcessingFilter;
34  import org.acegisecurity.userdetails.User;
35  import org.acegisecurity.userdetails.UserDetails;
36  
37  
38  /***
39   * Tests {@link CasAuthenticationProvider}.
40   *
41   * @author Ben Alex
42   * @version $Id: CasAuthenticationProviderTests.java,v 1.8 2005/11/30 01:23:36 benalex Exp $
43   */
44  public class CasAuthenticationProviderTests extends TestCase {
45      //~ Constructors ===========================================================
46  
47      public CasAuthenticationProviderTests() {
48          super();
49      }
50  
51      public CasAuthenticationProviderTests(String arg0) {
52          super(arg0);
53      }
54  
55      //~ Methods ================================================================
56  
57      public final void setUp() throws Exception {
58          super.setUp();
59      }
60  
61      public static void main(String[] args) {
62          junit.textui.TestRunner.run(CasAuthenticationProviderTests.class);
63      }
64  
65      public void testAuthenticateStateful() throws Exception {
66          CasAuthenticationProvider cap = new CasAuthenticationProvider();
67          cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
68          cap.setCasProxyDecider(new MockProxyDecider(true));
69          cap.setKey("qwerty");
70  
71          StatelessTicketCache cache = new MockStatelessTicketCache();
72          cap.setStatelessTicketCache(cache);
73          cap.setTicketValidator(new MockTicketValidator(true));
74          cap.afterPropertiesSet();
75  
76          UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(CasProcessingFilter.CAS_STATEFUL_IDENTIFIER,
77                  "ST-123");
78  
79          Authentication result = cap.authenticate(token);
80  
81          // Confirm ST-123 was NOT added to the cache
82          assertTrue(cache.getByTicketId("ST-456") == null);
83  
84          if (!(result instanceof CasAuthenticationToken)) {
85              fail("Should have returned a CasAuthenticationToken");
86          }
87  
88          CasAuthenticationToken casResult = (CasAuthenticationToken) result;
89          assertEquals("marissa", casResult.getPrincipal());
90          assertEquals("PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt",
91              casResult.getProxyGrantingTicketIou());
92          assertEquals("https://localhost/portal/j_acegi_cas_security_check",
93              casResult.getProxyList().get(0));
94          assertEquals("ST-123", casResult.getCredentials());
95          assertEquals(new GrantedAuthorityImpl("ROLE_A"),
96              casResult.getAuthorities()[0]);
97          assertEquals(new GrantedAuthorityImpl("ROLE_B"),
98              casResult.getAuthorities()[1]);
99          assertEquals(cap.getKey().hashCode(), casResult.getKeyHash());
100 
101         // Now confirm the CasAuthenticationToken is automatically re-accepted.
102         // To ensure TicketValidator not called again, set it to deliver an exception...
103         cap.setTicketValidator(new MockTicketValidator(false));
104 
105         Authentication laterResult = cap.authenticate(result);
106         assertEquals(result, laterResult);
107     }
108 
109     public void testAuthenticateStateless() throws Exception {
110         CasAuthenticationProvider cap = new CasAuthenticationProvider();
111         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
112         cap.setCasProxyDecider(new MockProxyDecider(true));
113         cap.setKey("qwerty");
114 
115         StatelessTicketCache cache = new MockStatelessTicketCache();
116         cap.setStatelessTicketCache(cache);
117         cap.setTicketValidator(new MockTicketValidator(true));
118         cap.afterPropertiesSet();
119 
120         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(CasProcessingFilter.CAS_STATELESS_IDENTIFIER,
121                 "ST-456");
122 
123         Authentication result = cap.authenticate(token);
124 
125         // Confirm ST-456 was added to the cache
126         assertTrue(cache.getByTicketId("ST-456") != null);
127 
128         if (!(result instanceof CasAuthenticationToken)) {
129             fail("Should have returned a CasAuthenticationToken");
130         }
131 
132         assertEquals("marissa", result.getPrincipal());
133         assertEquals("ST-456", result.getCredentials());
134 
135         // Now try to authenticate again. To ensure TicketValidator not
136         // called again, set it to deliver an exception...
137         cap.setTicketValidator(new MockTicketValidator(false));
138 
139         // Previously created UsernamePasswordAuthenticationToken is OK
140         Authentication newResult = cap.authenticate(token);
141         assertEquals("marissa", newResult.getPrincipal());
142         assertEquals("ST-456", newResult.getCredentials());
143     }
144 
145     public void testDetectsAMissingTicketId() throws Exception {
146         CasAuthenticationProvider cap = new CasAuthenticationProvider();
147         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
148         cap.setCasProxyDecider(new MockProxyDecider(true));
149         cap.setKey("qwerty");
150 
151         StatelessTicketCache cache = new MockStatelessTicketCache();
152         cap.setStatelessTicketCache(cache);
153         cap.setTicketValidator(new MockTicketValidator(true));
154         cap.afterPropertiesSet();
155 
156         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(CasProcessingFilter.CAS_STATEFUL_IDENTIFIER,
157                 "");
158 
159         try {
160             Authentication result = cap.authenticate(token);
161             fail("Should have thrown BadCredentialsException");
162         } catch (BadCredentialsException expected) {
163             assertEquals("Failed to provide a CAS service ticket to validate",
164                 expected.getMessage());
165         }
166     }
167 
168     public void testDetectsAnInvalidKey() throws Exception {
169         CasAuthenticationProvider cap = new CasAuthenticationProvider();
170         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
171         cap.setCasProxyDecider(new MockProxyDecider(true));
172         cap.setKey("qwerty");
173 
174         StatelessTicketCache cache = new MockStatelessTicketCache();
175         cap.setStatelessTicketCache(cache);
176         cap.setTicketValidator(new MockTicketValidator(true));
177         cap.afterPropertiesSet();
178 
179         CasAuthenticationToken token = new CasAuthenticationToken("WRONG_KEY",
180                 "test", "credentials",
181                 new GrantedAuthority[] {new GrantedAuthorityImpl("XX")},
182                 makeUserDetails(), new Vector(), "IOU-xxx");
183 
184         try {
185             Authentication result = cap.authenticate(token);
186             fail("Should have thrown BadCredentialsException");
187         } catch (BadCredentialsException expected) {
188             assertEquals("The presented CasAuthenticationToken does not contain the expected key",
189                 expected.getMessage());
190         }
191     }
192 
193     public void testDetectsMissingAuthoritiesPopulator()
194         throws Exception {
195         CasAuthenticationProvider cap = new CasAuthenticationProvider();
196         cap.setCasProxyDecider(new MockProxyDecider());
197         cap.setKey("qwerty");
198         cap.setStatelessTicketCache(new MockStatelessTicketCache());
199         cap.setTicketValidator(new MockTicketValidator(true));
200 
201         try {
202             cap.afterPropertiesSet();
203             fail("Should have thrown IllegalArgumentException");
204         } catch (IllegalArgumentException expected) {
205             assertEquals("A casAuthoritiesPopulator must be set",
206                 expected.getMessage());
207         }
208     }
209 
210     public void testDetectsMissingKey() throws Exception {
211         CasAuthenticationProvider cap = new CasAuthenticationProvider();
212         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
213         cap.setCasProxyDecider(new MockProxyDecider());
214         cap.setStatelessTicketCache(new MockStatelessTicketCache());
215         cap.setTicketValidator(new MockTicketValidator(true));
216 
217         try {
218             cap.afterPropertiesSet();
219             fail("Should have thrown IllegalArgumentException");
220         } catch (IllegalArgumentException expected) {
221             assertEquals("A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated",
222                 expected.getMessage());
223         }
224     }
225 
226     public void testDetectsMissingProxyDecider() throws Exception {
227         CasAuthenticationProvider cap = new CasAuthenticationProvider();
228         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
229         cap.setKey("qwerty");
230         cap.setStatelessTicketCache(new MockStatelessTicketCache());
231         cap.setTicketValidator(new MockTicketValidator(true));
232 
233         try {
234             cap.afterPropertiesSet();
235             fail("Should have thrown IllegalArgumentException");
236         } catch (IllegalArgumentException expected) {
237             assertEquals("A casProxyDecider must be set", expected.getMessage());
238         }
239     }
240 
241     public void testDetectsMissingStatelessTicketCache()
242         throws Exception {
243         CasAuthenticationProvider cap = new CasAuthenticationProvider();
244         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
245         cap.setCasProxyDecider(new MockProxyDecider());
246         cap.setKey("qwerty");
247         cap.setTicketValidator(new MockTicketValidator(true));
248 
249         try {
250             cap.afterPropertiesSet();
251             fail("Should have thrown IllegalArgumentException");
252         } catch (IllegalArgumentException expected) {
253             assertEquals("A statelessTicketCache must be set",
254                 expected.getMessage());
255         }
256     }
257 
258     public void testDetectsMissingTicketValidator() throws Exception {
259         CasAuthenticationProvider cap = new CasAuthenticationProvider();
260         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
261         cap.setCasProxyDecider(new MockProxyDecider(true));
262         cap.setKey("qwerty");
263         cap.setStatelessTicketCache(new MockStatelessTicketCache());
264 
265         try {
266             cap.afterPropertiesSet();
267             fail("Should have thrown IllegalArgumentException");
268         } catch (IllegalArgumentException expected) {
269             assertEquals("A ticketValidator must be set", expected.getMessage());
270         }
271     }
272 
273     public void testGettersSetters() throws Exception {
274         CasAuthenticationProvider cap = new CasAuthenticationProvider();
275         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
276         cap.setCasProxyDecider(new MockProxyDecider());
277         cap.setKey("qwerty");
278         cap.setStatelessTicketCache(new MockStatelessTicketCache());
279         cap.setTicketValidator(new MockTicketValidator(true));
280         cap.afterPropertiesSet();
281 
282         assertTrue(cap.getCasAuthoritiesPopulator() != null);
283         assertTrue(cap.getCasProxyDecider() != null);
284         assertEquals("qwerty", cap.getKey());
285         assertTrue(cap.getStatelessTicketCache() != null);
286         assertTrue(cap.getTicketValidator() != null);
287     }
288 
289     public void testIgnoresClassesItDoesNotSupport() throws Exception {
290         CasAuthenticationProvider cap = new CasAuthenticationProvider();
291         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
292         cap.setCasProxyDecider(new MockProxyDecider());
293         cap.setKey("qwerty");
294         cap.setStatelessTicketCache(new MockStatelessTicketCache());
295         cap.setTicketValidator(new MockTicketValidator(true));
296         cap.afterPropertiesSet();
297 
298         TestingAuthenticationToken token = new TestingAuthenticationToken("user",
299                 "password",
300                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")});
301         assertFalse(cap.supports(TestingAuthenticationToken.class));
302 
303         // Try it anyway
304         assertEquals(null, cap.authenticate(token));
305     }
306 
307     public void testIgnoresUsernamePasswordAuthenticationTokensWithoutCasIdentifiersAsPrincipal()
308         throws Exception {
309         CasAuthenticationProvider cap = new CasAuthenticationProvider();
310         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
311         cap.setCasProxyDecider(new MockProxyDecider());
312         cap.setKey("qwerty");
313         cap.setStatelessTicketCache(new MockStatelessTicketCache());
314         cap.setTicketValidator(new MockTicketValidator(true));
315         cap.afterPropertiesSet();
316 
317         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("some_normal_user",
318                 "password",
319                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")});
320         assertEquals(null, cap.authenticate(token));
321     }
322 
323     public void testSupports() {
324         CasAuthenticationProvider cap = new CasAuthenticationProvider();
325         assertTrue(cap.supports(UsernamePasswordAuthenticationToken.class));
326         assertTrue(cap.supports(CasAuthenticationToken.class));
327     }
328 
329     private UserDetails makeUserDetails() {
330         return new User("user", "password", true, true, true, true,
331             new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
332                     "ROLE_TWO")});
333     }
334 
335     //~ Inner Classes ==========================================================
336 
337     private class MockAuthoritiesPopulator implements CasAuthoritiesPopulator {
338         public UserDetails getUserDetails(String casUserId)
339             throws AuthenticationException {
340             return new User("user", "password", true, true, true, true,
341                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl(
342                         "ROLE_B")});
343         }
344     }
345 
346     private class MockProxyDecider implements CasProxyDecider {
347         private boolean acceptProxy;
348 
349         public MockProxyDecider(boolean acceptProxy) {
350             this.acceptProxy = acceptProxy;
351         }
352 
353         private MockProxyDecider() {
354             super();
355         }
356 
357         public void confirmProxyListTrusted(List proxyList)
358             throws ProxyUntrustedException {
359             if (acceptProxy) {
360                 return;
361             } else {
362                 throw new ProxyUntrustedException("As requested from mock");
363             }
364         }
365     }
366 
367     private class MockStatelessTicketCache implements StatelessTicketCache {
368         private Map cache = new HashMap();
369 
370         public CasAuthenticationToken getByTicketId(String serviceTicket) {
371             return (CasAuthenticationToken) cache.get(serviceTicket);
372         }
373 
374         public void putTicketInCache(CasAuthenticationToken token) {
375             cache.put(token.getCredentials().toString(), token);
376         }
377 
378         public void removeTicketFromCache(CasAuthenticationToken token) {
379             throw new UnsupportedOperationException(
380                 "mock method not implemented");
381         }
382 
383         public void removeTicketFromCache(String serviceTicket) {
384             throw new UnsupportedOperationException(
385                 "mock method not implemented");
386         }
387     }
388 
389     private class MockTicketValidator extends AbstractTicketValidator {
390         private boolean returnTicket;
391 
392         public MockTicketValidator(boolean returnTicket) {
393             this.returnTicket = returnTicket;
394         }
395 
396         private MockTicketValidator() {
397             super();
398         }
399 
400         public TicketResponse confirmTicketValid(String serviceTicket)
401             throws AuthenticationException {
402             if (returnTicket) {
403                 List list = new Vector();
404                 list.add("https://localhost/portal/j_acegi_cas_security_check");
405 
406                 return new TicketResponse("marissa", list,
407                     "PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt");
408             }
409 
410             throw new BadCredentialsException("As requested from mock");
411         }
412     }
413 }