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