    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 }