/* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.security.cas.authentication; import static org.mockito.Mockito.*; import static org.assertj.core.api.Assertions.*; import org.jasig.cas.client.validation.Assertion; import org.jasig.cas.client.validation.AssertionImpl; import org.jasig.cas.client.validation.TicketValidationException; import org.jasig.cas.client.validation.TicketValidator; import org.junit.*; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.cas.ServiceProperties; import org.springframework.security.cas.web.CasAuthenticationFilter; import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.WebAuthenticationDetails; import java.util.*; /** * Tests {@link CasAuthenticationProvider}. * * @author Ben Alex * @author Scott Battaglia */ @SuppressWarnings("unchecked") public class CasAuthenticationProviderTests { // ~ Methods // ======================================================================================================== private UserDetails makeUserDetails() { return new User("user", "password", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); } private UserDetails makeUserDetailsFromAuthoritiesPopulator() { return new User("user", "password", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_A", "ROLE_B")); } private ServiceProperties makeServiceProperties() { final ServiceProperties serviceProperties = new ServiceProperties(); serviceProperties.setSendRenew(false); serviceProperties.setService("http://test.com"); return serviceProperties; } @Test public void statefulAuthenticationIsSuccessful() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); StatelessTicketCache cache = new MockStatelessTicketCache(); cap.setStatelessTicketCache(cache); cap.setServiceProperties(makeServiceProperties()); cap.setTicketValidator(new MockTicketValidator(true)); cap.afterPropertiesSet(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "ST-123"); token.setDetails("details"); Authentication result = cap.authenticate(token); // Confirm ST-123 was NOT added to the cache assertThat(cache.getByTicketId("ST-456") == null).isTrue(); if (!(result instanceof CasAuthenticationToken)) { fail("Should have returned a CasAuthenticationToken"); } CasAuthenticationToken casResult = (CasAuthenticationToken) result; assertThat(casResult.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator()); assertThat(casResult.getCredentials()).isEqualTo("ST-123"); assertThat(casResult.getAuthorities()).contains( new SimpleGrantedAuthority("ROLE_A")); assertThat(casResult.getAuthorities()).contains( new SimpleGrantedAuthority("ROLE_B")); assertThat(casResult.getKeyHash()).isEqualTo(cap.getKey().hashCode()); assertThat(casResult.getDetails()).isEqualTo("details"); // Now confirm the CasAuthenticationToken is automatically re-accepted. // To ensure TicketValidator not called again, set it to deliver an exception... cap.setTicketValidator(new MockTicketValidator(false)); Authentication laterResult = cap.authenticate(result); assertThat(laterResult).isEqualTo(result); } @Test public void statelessAuthenticationIsSuccessful() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); StatelessTicketCache cache = new MockStatelessTicketCache(); cap.setStatelessTicketCache(cache); cap.setTicketValidator(new MockTicketValidator(true)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, "ST-456"); token.setDetails("details"); Authentication result = cap.authenticate(token); // Confirm ST-456 was added to the cache assertThat(cache.getByTicketId("ST-456") != null).isTrue(); if (!(result instanceof CasAuthenticationToken)) { fail("Should have returned a CasAuthenticationToken"); } assertThat(result.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator()); assertThat(result.getCredentials()).isEqualTo("ST-456"); assertThat(result.getDetails()).isEqualTo("details"); // Now try to authenticate again. To ensure TicketValidator not // called again, set it to deliver an exception... cap.setTicketValidator(new MockTicketValidator(false)); // Previously created UsernamePasswordAuthenticationToken is OK Authentication newResult = cap.authenticate(token); assertThat(newResult.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator()); assertThat(newResult.getCredentials()).isEqualTo("ST-456"); } @Test public void authenticateAllNullService() throws Exception { String serviceUrl = "https://service/context"; ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class); when(details.getServiceUrl()).thenReturn(serviceUrl); TicketValidator validator = mock(TicketValidator.class); when(validator.validate(any(String.class), any(String.class))).thenReturn( new AssertionImpl("rod")); ServiceProperties serviceProperties = makeServiceProperties(); serviceProperties.setAuthenticateAllArtifacts(true); CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setTicketValidator(validator); cap.setServiceProperties(serviceProperties); cap.afterPropertiesSet(); String ticket = "ST-456"; UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket); Authentication result = cap.authenticate(token); } @Test public void authenticateAllAuthenticationIsSuccessful() throws Exception { String serviceUrl = "https://service/context"; ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class); when(details.getServiceUrl()).thenReturn(serviceUrl); TicketValidator validator = mock(TicketValidator.class); when(validator.validate(any(String.class), any(String.class))).thenReturn( new AssertionImpl("rod")); ServiceProperties serviceProperties = makeServiceProperties(); serviceProperties.setAuthenticateAllArtifacts(true); CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setTicketValidator(validator); cap.setServiceProperties(serviceProperties); cap.afterPropertiesSet(); String ticket = "ST-456"; UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket); Authentication result = cap.authenticate(token); verify(validator).validate(ticket, serviceProperties.getService()); serviceProperties.setAuthenticateAllArtifacts(true); result = cap.authenticate(token); verify(validator, times(2)).validate(ticket, serviceProperties.getService()); token.setDetails(details); result = cap.authenticate(token); verify(validator).validate(ticket, serviceUrl); serviceProperties.setAuthenticateAllArtifacts(false); serviceProperties.setService(null); cap.setServiceProperties(serviceProperties); cap.afterPropertiesSet(); result = cap.authenticate(token); verify(validator, times(2)).validate(ticket, serviceUrl); token.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())); try { cap.authenticate(token); fail("Expected Exception"); } catch (IllegalStateException success) { } cap.setServiceProperties(null); cap.afterPropertiesSet(); try { cap.authenticate(token); fail("Expected Exception"); } catch (IllegalStateException success) { } } @Test(expected = BadCredentialsException.class) public void missingTicketIdIsDetected() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); StatelessTicketCache cache = new MockStatelessTicketCache(); cap.setStatelessTicketCache(cache); cap.setTicketValidator(new MockTicketValidator(true)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, ""); cap.authenticate(token); } @Test(expected = BadCredentialsException.class) public void invalidKeyIsDetected() throws Exception { final Assertion assertion = new AssertionImpl("test"); CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); StatelessTicketCache cache = new MockStatelessTicketCache(); cap.setStatelessTicketCache(cache); cap.setTicketValidator(new MockTicketValidator(true)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); CasAuthenticationToken token = new CasAuthenticationToken("WRONG_KEY", makeUserDetails(), "credentials", AuthorityUtils.createAuthorityList("XX"), makeUserDetails(), assertion); cap.authenticate(token); } @Test(expected = IllegalArgumentException.class) public void detectsMissingAuthoritiesPopulator() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setKey("qwerty"); cap.setStatelessTicketCache(new MockStatelessTicketCache()); cap.setTicketValidator(new MockTicketValidator(true)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); } @Test(expected = IllegalArgumentException.class) public void detectsMissingKey() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setStatelessTicketCache(new MockStatelessTicketCache()); cap.setTicketValidator(new MockTicketValidator(true)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); } @Test(expected = IllegalArgumentException.class) public void detectsMissingStatelessTicketCache() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); // set this explicitly to null to test failure cap.setStatelessTicketCache(null); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setTicketValidator(new MockTicketValidator(true)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); } @Test(expected = IllegalArgumentException.class) public void detectsMissingTicketValidator() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setStatelessTicketCache(new MockStatelessTicketCache()); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); } @Test public void gettersAndSettersMatch() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setStatelessTicketCache(new MockStatelessTicketCache()); cap.setTicketValidator(new MockTicketValidator(true)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); // TODO disabled because why do we need to expose this? // assertThat(cap.getUserDetailsService() != null).isTrue(); assertThat(cap.getKey()).isEqualTo("qwerty"); assertThat(cap.getStatelessTicketCache() != null).isTrue(); assertThat(cap.getTicketValidator() != null).isTrue(); } @Test public void ignoresClassesItDoesNotSupport() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setStatelessTicketCache(new MockStatelessTicketCache()); cap.setTicketValidator(new MockTicketValidator(true)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); TestingAuthenticationToken token = new TestingAuthenticationToken("user", "password", "ROLE_A"); assertThat(cap.supports(TestingAuthenticationToken.class)).isFalse(); // Try it anyway assertThat(cap.authenticate(token)).isEqualTo(null); } @Test public void ignoresUsernamePasswordAuthenticationTokensWithoutCasIdentifiersAsPrincipal() throws Exception { CasAuthenticationProvider cap = new CasAuthenticationProvider(); cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); cap.setKey("qwerty"); cap.setStatelessTicketCache(new MockStatelessTicketCache()); cap.setTicketValidator(new MockTicketValidator(true)); cap.setServiceProperties(makeServiceProperties()); cap.afterPropertiesSet(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( "some_normal_user", "password", AuthorityUtils.createAuthorityList("ROLE_A")); assertThat(cap.authenticate(token)).isEqualTo(null); } @Test public void supportsRequiredTokens() { CasAuthenticationProvider cap = new CasAuthenticationProvider(); assertThat(cap.supports(UsernamePasswordAuthenticationToken.class)).isTrue(); assertThat(cap.supports(CasAuthenticationToken.class)).isTrue(); } // ~ Inner Classes // ================================================================================================== private class MockAuthoritiesPopulator implements AuthenticationUserDetailsService { public UserDetails loadUserDetails(final Authentication token) throws UsernameNotFoundException { return makeUserDetailsFromAuthoritiesPopulator(); } } private class MockStatelessTicketCache implements StatelessTicketCache { private Map<String, CasAuthenticationToken> cache = new HashMap<String, CasAuthenticationToken>(); public CasAuthenticationToken getByTicketId(String serviceTicket) { return cache.get(serviceTicket); } public void putTicketInCache(CasAuthenticationToken token) { cache.put(token.getCredentials().toString(), token); } public void removeTicketFromCache(CasAuthenticationToken token) { throw new UnsupportedOperationException("mock method not implemented"); } public void removeTicketFromCache(String serviceTicket) { throw new UnsupportedOperationException("mock method not implemented"); } } private class MockTicketValidator implements TicketValidator { private boolean returnTicket; public MockTicketValidator(boolean returnTicket) { this.returnTicket = returnTicket; } public Assertion validate(final String ticket, final String service) throws TicketValidationException { if (returnTicket) { return new AssertionImpl("rod"); } throw new BadCredentialsException("As requested from mock"); } } }