diff --git a/config/src/main/java/org/springframework/security/config/NamespaceAuthenticationManager.java b/config/src/main/java/org/springframework/security/config/NamespaceAuthenticationManager.java index 5fe2473aa4..39df2f419b 100644 --- a/config/src/main/java/org/springframework/security/config/NamespaceAuthenticationManager.java +++ b/config/src/main/java/org/springframework/security/config/NamespaceAuthenticationManager.java @@ -30,8 +30,6 @@ public class NamespaceAuthenticationManager extends ProviderManager implements B public void afterPropertiesSet() throws Exception { Assert.notNull(providerBeanNames, "provideBeanNames has not been set"); Assert.notEmpty(providerBeanNames, "No authentication providers were found in the application context"); - - super.afterPropertiesSet(); } /** diff --git a/core/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java b/core/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java index be7d34b4dd..cd541288ce 100644 --- a/core/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java +++ b/core/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java @@ -106,7 +106,7 @@ public class MapBasedMethodSecurityMetadataSource extends AbstractFallbackMethod * @param name type and method name, separated by a dot * @param attr required authorities associated with the method */ - public void addSecureMethod(String name, List attr) { + private void addSecureMethod(String name, List attr) { int lastDotIndex = name.lastIndexOf("."); if (lastDotIndex == -1) { diff --git a/core/src/main/java/org/springframework/security/authentication/AuthenticationEventPublisher.java b/core/src/main/java/org/springframework/security/authentication/AuthenticationEventPublisher.java new file mode 100644 index 0000000000..dada6df42e --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/AuthenticationEventPublisher.java @@ -0,0 +1,17 @@ +package org.springframework.security.authentication; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * + * @author Luke Taylor + * @version $Id$ + * @since 3.0 + */ +public interface AuthenticationEventPublisher { + + void publishAuthenticationSuccess(Authentication authentication); + + void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication); +} diff --git a/core/src/main/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisher.java b/core/src/main/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisher.java new file mode 100644 index 0000000000..2ea9f955d5 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisher.java @@ -0,0 +1,134 @@ +package org.springframework.security.authentication; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.security.authentication.concurrent.ConcurrentLoginException; +import org.springframework.security.authentication.event.AbstractAuthenticationEvent; +import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; +import org.springframework.security.authentication.event.AuthenticationFailureConcurrentLoginEvent; +import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent; +import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent; +import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent; +import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent; +import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent; +import org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent; +import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent; +import org.springframework.security.authentication.event.AuthenticationSuccessEvent; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.util.Assert; + +/** + * The default strategy used by ProviderManager for publishing authentication events. + *

+ * Maps well-known AuthenticationException types to events and publishes them via the + * application context. If configured as a bean, it will pick up the ApplicationEventPublisher automatically. + * Otherwise, the constructor which takes the publisher as an argument should be used. + *

+ * The exception-mapping system can be fine-tuned by setting the additionalExceptionMappings as a + * java.util.Properties object. In the properties object, each of the keys represent the fully qualified + * classname of the exception, and each of the values represent the name of an event class which subclasses + * {@link org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent} + * and provides its constructor. The additionalExceptionMappings will be merged with the default ones. + * + * @author Luke Taylor + * @version $Id$ + * @since 3.0 + */ +public class DefaultAuthenticationEventPublisher implements AuthenticationEventPublisher, + ApplicationEventPublisherAware { + private final Log logger = LogFactory.getLog(getClass()); + + private ApplicationEventPublisher applicationEventPublisher; + private final Properties exceptionMappings; + + public DefaultAuthenticationEventPublisher() { + this(null); + } + + public DefaultAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + exceptionMappings = new Properties(); + exceptionMappings.put(AccountExpiredException.class.getName(), + AuthenticationFailureExpiredEvent.class.getName()); + exceptionMappings.put(AuthenticationServiceException.class.getName(), + AuthenticationFailureServiceExceptionEvent.class.getName()); + exceptionMappings.put(LockedException.class.getName(), + AuthenticationFailureLockedEvent.class.getName()); + exceptionMappings.put(CredentialsExpiredException.class.getName(), + AuthenticationFailureCredentialsExpiredEvent.class.getName()); + exceptionMappings.put(DisabledException.class.getName(), + AuthenticationFailureDisabledEvent.class.getName()); + exceptionMappings.put(BadCredentialsException.class.getName(), + AuthenticationFailureBadCredentialsEvent.class.getName()); + exceptionMappings.put(UsernameNotFoundException.class.getName(), + AuthenticationFailureBadCredentialsEvent.class.getName()); + exceptionMappings.put(ConcurrentLoginException.class.getName(), + AuthenticationFailureConcurrentLoginEvent.class.getName()); + exceptionMappings.put(ProviderNotFoundException.class.getName(), + AuthenticationFailureProviderNotFoundEvent.class.getName()); + exceptionMappings.put("org.springframework.security.authentication.cas.ProxyUntrustedException", + AuthenticationFailureProxyUntrustedEvent.class.getName()); + } + + public void publishAuthenticationSuccess(Authentication authentication) { + if (applicationEventPublisher != null) { + applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(authentication)); + } + } + + public void publishAuthenticationFailure(AuthenticationException exception, + Authentication authentication) { + String className = exceptionMappings.getProperty(exception.getClass().getName()); + AbstractAuthenticationEvent event = null; + + if (className != null) { + try { + Class clazz = getClass().getClassLoader().loadClass(className); + Constructor constructor = clazz.getConstructor(new Class[] { + Authentication.class, AuthenticationException.class + }); + Object obj = constructor.newInstance(authentication, exception); + Assert.isInstanceOf(AbstractAuthenticationEvent.class, obj, "Must be an AbstractAuthenticationEvent"); + event = (AbstractAuthenticationEvent) obj; + } catch (ClassNotFoundException ignored) {} + catch (NoSuchMethodException ignored) {} + catch (IllegalAccessException ignored) {} + catch (InstantiationException ignored) {} + catch (InvocationTargetException ignored) {} + } + + if (event != null) { + if (applicationEventPublisher != null) { + applicationEventPublisher.publishEvent(event); + } + } else { + if (logger.isDebugEnabled()) { + logger.debug("No event was found for the exception " + exception.getClass().getName()); + } + } + } + + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + /** + * Sets additional exception to event mappings. These are automatically merged with the default + * exception to event mappings that ProviderManager defines. + * + * @param additionalExceptionMappings where keys are the fully-qualified string name of the exception class and the + * values are the fully-qualified string name of the event class to fire. + */ + public void setAdditionalExceptionMappings(Properties additionalExceptionMappings) { + Assert.notNull(additionalExceptionMappings, "The exceptionMappings object must not be null"); + exceptionMappings.putAll(additionalExceptionMappings); + } +} diff --git a/core/src/main/java/org/springframework/security/authentication/ProviderManager.java b/core/src/main/java/org/springframework/security/authentication/ProviderManager.java index 19d7f72338..a6b57826ea 100644 --- a/core/src/main/java/org/springframework/security/authentication/ProviderManager.java +++ b/core/src/main/java/org/springframework/security/authentication/ProviderManager.java @@ -15,38 +15,18 @@ package org.springframework.security.authentication; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.List; -import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.authentication.concurrent.ConcurrentLoginException; import org.springframework.security.authentication.concurrent.ConcurrentSessionController; -import org.springframework.security.authentication.concurrent.NullConcurrentSessionController; -import org.springframework.security.authentication.event.AbstractAuthenticationEvent; -import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; -import org.springframework.security.authentication.event.AuthenticationFailureConcurrentLoginEvent; -import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent; -import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent; -import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent; -import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent; -import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent; -import org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent; -import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent; -import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.util.Assert; @@ -69,73 +49,41 @@ import org.springframework.util.Assert; * concurrent session controller throws a {@link ConcurrentLoginException}. In both these cases, no further providers * in the list will be queried. * + *

Event Publishing

*

- * If a valid Authentication is returned by an AuthenticationProvider, the - * ProviderManager will publish an - * {@link org.springframework.security.authentication.event.AuthenticationSuccessEvent}. If an - * AuthenticationException is detected, the final AuthenticationException thrown will be - * used to publish an appropriate failure event. By default ProviderManager maps common exceptions to - * events, but this can be fine-tuned by providing a new exceptionMappingsjava.util.Properties - * object. In the properties object, each of the keys represent the fully qualified classname of the exception, and - * each of the values represent the name of an event class which subclasses - * {@link org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent} - * and provides its constructor. - * + * Authentication event publishing is delegated to the configured {@link AuthenticationEventPublisher} which defaults + * to a null implementation which doesn't publish events, so if you are configuring the bean yourself you must inject + * a publisher bean if you want to receive events. The standard implementation is {@link DefaultAuthenticationEventPublisher} + * which maps common exceptions to events (in the case of authentication failure) and publishes an + * {@link org.springframework.security.authentication.event.AuthenticationSuccessEvent AuthenticationSuccessEvent} if + * authentication succeeds. If you are using the namespace then an instance of this bean will be used automatically by + * the <http> configuration, so you will receive events from the web part of your application automatically. + *

+ * Note that the implementation also publishes authentication failure events when it obtains an authentication result + * (or an exception) from the "parent" AuthenticationManager if one has been set. So in this situation, the + * parent should not generally be configured to publish events or there will be duplicates. * * @author Ben Alex + * @author Luke Taylor * @version $Id$ * @see ConcurrentSessionController + * @see DefaultAuthenticationEventPublisher */ -public class ProviderManager extends AbstractAuthenticationManager implements InitializingBean, MessageSourceAware, - ApplicationEventPublisherAware { +public class ProviderManager extends AbstractAuthenticationManager implements MessageSourceAware { //~ Static fields/initializers ===================================================================================== private static final Log logger = LogFactory.getLog(ProviderManager.class); - private static final Properties DEFAULT_EXCEPTION_MAPPINGS = new Properties(); //~ Instance fields ================================================================================================ - private ApplicationEventPublisher applicationEventPublisher; + private AuthenticationEventPublisher eventPublisher = new NullEventPublisher(); private ConcurrentSessionController sessionController = new NullConcurrentSessionController(); private List providers; protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - private Properties exceptionMappings = new Properties(); - private Properties additionalExceptionMappings = new Properties(); - - static { - DEFAULT_EXCEPTION_MAPPINGS.put(AccountExpiredException.class.getName(), - AuthenticationFailureExpiredEvent.class.getName()); - DEFAULT_EXCEPTION_MAPPINGS.put(AuthenticationServiceException.class.getName(), - AuthenticationFailureServiceExceptionEvent.class.getName()); - DEFAULT_EXCEPTION_MAPPINGS.put(LockedException.class.getName(), - AuthenticationFailureLockedEvent.class.getName()); - DEFAULT_EXCEPTION_MAPPINGS.put(CredentialsExpiredException.class.getName(), - AuthenticationFailureCredentialsExpiredEvent.class.getName()); - DEFAULT_EXCEPTION_MAPPINGS.put(DisabledException.class.getName(), - AuthenticationFailureDisabledEvent.class.getName()); - DEFAULT_EXCEPTION_MAPPINGS.put(BadCredentialsException.class.getName(), - AuthenticationFailureBadCredentialsEvent.class.getName()); - DEFAULT_EXCEPTION_MAPPINGS.put(UsernameNotFoundException.class.getName(), - AuthenticationFailureBadCredentialsEvent.class.getName()); - DEFAULT_EXCEPTION_MAPPINGS.put(ConcurrentLoginException.class.getName(), - AuthenticationFailureConcurrentLoginEvent.class.getName()); - DEFAULT_EXCEPTION_MAPPINGS.put(ProviderNotFoundException.class.getName(), - AuthenticationFailureProviderNotFoundEvent.class.getName()); - DEFAULT_EXCEPTION_MAPPINGS.put("org.springframework.security.authentication.cas.ProxyUntrustedException", - AuthenticationFailureProxyUntrustedEvent.class.getName()); - } - - public ProviderManager() { - exceptionMappings.putAll(DEFAULT_EXCEPTION_MAPPINGS); - } + private AuthenticationManager parent; //~ Methods ======================================================================================================== - public void afterPropertiesSet() throws Exception { - Assert.notNull(this.messages, "A message source must be set"); - exceptionMappings.putAll(additionalExceptionMappings); - } - /** * Attempts to authenticate the passed {@link Authentication} object. *

@@ -157,6 +105,7 @@ public class ProviderManager extends AbstractAuthenticationManager implements In public Authentication doAuthentication(Authentication authentication) throws AuthenticationException { Class toTest = authentication.getClass(); AuthenticationException lastException = null; + Authentication result = null; for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { @@ -165,81 +114,59 @@ public class ProviderManager extends AbstractAuthenticationManager implements In logger.debug("Authentication attempt using " + provider.getClass().getName()); - Authentication result; - try { result = provider.authenticate(authentication); - if (result == null) { - continue; + if (result != null) { + copyDetails(authentication, result); + break; } } catch (AccountStatusException e) { // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status - lastException = e; - break; + eventPublisher.publishAuthenticationFailure(e, authentication); + throw e; } catch (AuthenticationException e) { lastException = e; - continue; } - - assert result != null; - - copyDetails(authentication, result); - - try { - sessionController.checkAuthenticationAllowed(result); - } catch (AuthenticationException e) { - // SEC-546: Avoid polling additional providers if concurrent login check fails - lastException = e; - break; - } - - sessionController.registerSuccessfulAuthentication(result); - publishEvent(new AuthenticationSuccessEvent(result)); - - return result; } + if (result == null && parent != null) { + // Allow the parent to try. + try { + result = parent.authenticate(authentication); + } catch (ProviderNotFoundException e) { + // ignore as we will throw below if no other exception occurred prior to calling parent and the parent + // may throw ProviderNotFound even though a provider in the child already handled the request + } catch (AuthenticationException e) { + lastException = e; + } + } + + // Finally check if the concurrent session controller will allow authentication + try { + if (result != null) { + sessionController.checkAuthenticationAllowed(result); + sessionController.registerSuccessfulAuthentication(result); + eventPublisher.publishAuthenticationSuccess(result); + + return result; + } + } catch (AuthenticationException e) { + lastException = e; + } + + // Session control failed, parent was null, or didn't authenticate (or throw an exception). + if (lastException == null) { lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound", new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}")); } - publishAuthenticationFailure(lastException, authentication); + eventPublisher.publishAuthenticationFailure(lastException, authentication); throw lastException; } - private void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) { - String className = exceptionMappings.getProperty(exception.getClass().getName()); - AbstractAuthenticationEvent event = null; - - if (className != null) { - try { - Class clazz = getClass().getClassLoader().loadClass(className); - Constructor constructor = clazz.getConstructor(new Class[] { - Authentication.class, AuthenticationException.class - }); - Object obj = constructor.newInstance(authentication, exception); - Assert.isInstanceOf(AbstractAuthenticationEvent.class, obj, "Must be an AbstractAuthenticationEvent"); - event = (AbstractAuthenticationEvent) obj; - } catch (ClassNotFoundException ignored) {} - catch (NoSuchMethodException ignored) {} - catch (IllegalAccessException ignored) {} - catch (InstantiationException ignored) {} - catch (InvocationTargetException ignored) {} - } - - if (event != null) { - publishEvent(event); - } else { - if (logger.isDebugEnabled()) { - logger.debug("No event was found for the exception " + exception.getClass().getName()); - } - } - - } - /** * Copies the authentication details from a source Authentication object to a destination one, provided the * latter does not already have one set. @@ -273,14 +200,18 @@ public class ProviderManager extends AbstractAuthenticationManager implements In return sessionController; } - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } + public void setParent(AuthenticationManager parent) { + this.parent = parent; + } + + public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + /** * Sets the {@link AuthenticationProvider} objects to be used for authentication. * @@ -302,7 +233,6 @@ public class ProviderManager extends AbstractAuthenticationManager implements In /** * Set the {@link ConcurrentSessionController} to be used for limiting users' sessions. - * The {@link NullConcurrentSessionController} is used by default. * * @param sessionController {@link ConcurrentSessionController} */ @@ -310,20 +240,13 @@ public class ProviderManager extends AbstractAuthenticationManager implements In this.sessionController = sessionController; } - private void publishEvent(ApplicationEvent event) { - if (applicationEventPublisher != null) { - applicationEventPublisher.publishEvent(event); - } + private static final class NullEventPublisher implements AuthenticationEventPublisher { + public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {} + public void publishAuthenticationSuccess(Authentication authentication) {} } - /** - * Sets additional exception to event mappings. These are automatically merged with the default - * exception to event mappings that ProviderManager defines. - * - * @param additionalExceptionMappings where keys are the fully-qualified string name of the exception class and the - * values are the fully-qualified string name of the event class to fire. - */ - public void setAdditionalExceptionMappings(Properties additionalExceptionMappings) { - this.additionalExceptionMappings = additionalExceptionMappings; + private static final class NullConcurrentSessionController implements ConcurrentSessionController { + public void checkAuthenticationAllowed(Authentication request) {} + public void registerSuccessfulAuthentication(Authentication authentication) {} } } diff --git a/core/src/main/java/org/springframework/security/authentication/concurrent/NullConcurrentSessionController.java b/core/src/main/java/org/springframework/security/authentication/concurrent/NullConcurrentSessionController.java index 0e42e07860..84624abb9e 100644 --- a/core/src/main/java/org/springframework/security/authentication/concurrent/NullConcurrentSessionController.java +++ b/core/src/main/java/org/springframework/security/authentication/concurrent/NullConcurrentSessionController.java @@ -25,7 +25,7 @@ import org.springframework.security.core.AuthenticationException; * @author Ben Alex * @version $Id$ */ -public class NullConcurrentSessionController implements ConcurrentSessionController { +class NullConcurrentSessionController implements ConcurrentSessionController { //~ Methods ======================================================================================================== public void checkAuthenticationAllowed(Authentication request) diff --git a/core/src/test/java/org/springframework/security/MockApplicationEventPublisher.java b/core/src/test/java/org/springframework/security/MockApplicationEventPublisher.java deleted file mode 100644 index 77e3651c87..0000000000 --- a/core/src/test/java/org/springframework/security/MockApplicationEventPublisher.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.springframework.security; - -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEvent; - -/** - * @author Luke Taylor - * @version $Id$ - */ -public class MockApplicationEventPublisher implements ApplicationEventPublisher { - private Boolean expectedEvent; - private ApplicationEvent lastEvent; - - public MockApplicationEventPublisher() { - } - - public MockApplicationEventPublisher(boolean expectedEvent) { - this.expectedEvent = Boolean.valueOf(expectedEvent); - } - - public void publishEvent(ApplicationEvent event) { - if (expectedEvent != null && !expectedEvent.booleanValue()) { - throw new IllegalStateException("The ApplicationEventPublisher did not expect to receive this event"); - } - - lastEvent = event; - } - - public ApplicationEvent getLastEvent() { - return lastEvent; - } -} diff --git a/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java b/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java index 3ded575ecb..8c4a86067b 100644 --- a/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java +++ b/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java @@ -23,7 +23,7 @@ import java.util.Arrays; import java.util.List; import org.junit.Test; -import org.springframework.security.MockApplicationEventPublisher; +import org.springframework.context.MessageSource; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.ProviderNotFoundException; @@ -31,7 +31,6 @@ import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.concurrent.ConcurrentLoginException; import org.springframework.security.authentication.concurrent.ConcurrentSessionController; -import org.springframework.security.authentication.concurrent.NullConcurrentSessionController; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; @@ -43,6 +42,7 @@ import org.springframework.security.core.authority.AuthorityUtils; * @author Ben Alex * @version $Id$ */ +@SuppressWarnings("unchecked") public class ProviderManagerTests { @Test(expected=ProviderNotFoundException.class) @@ -51,46 +51,34 @@ public class ProviderManagerTests { AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); ProviderManager mgr = makeProviderManager(); - mgr.setApplicationEventPublisher(new MockApplicationEventPublisher(true)); + mgr.setMessageSource(mock(MessageSource.class)); mgr.authenticate(token); } @Test public void authenticationSucceedsWithSupportedTokenAndReturnsExpectedObject() throws Exception { - TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password","ROLE_ONE","ROLE_TWO"); + final Authentication a = mock(Authentication.class); + ProviderManager mgr = new ProviderManager(); + AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class); + mgr.setAuthenticationEventPublisher(publisher); + mgr.setProviders(Arrays.asList(createProviderWhichReturns(a))); - ProviderManager mgr = makeProviderManager(); - mgr.setApplicationEventPublisher(new MockApplicationEventPublisher(true)); - - Authentication result = mgr.authenticate(token); - - if (!(result instanceof TestingAuthenticationToken)) { - fail("Should have returned instance of TestingAuthenticationToken"); - } - - TestingAuthenticationToken castResult = (TestingAuthenticationToken) result; - assertEquals("Test", castResult.getPrincipal()); - assertEquals("Password", castResult.getCredentials()); - assertEquals(AuthorityUtils.createAuthorityList("ROLE_ONE","ROLE_TWO"), castResult.getAuthorities()); + Authentication result = mgr.authenticate(a); + assertEquals(a, result); + verify(publisher).publishAuthenticationSuccess(result); } @Test - public void authenticationSuccessWhenFirstProviderReturnsNullButSecondAuthenticates() { - TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password","ROLE_ONE","ROLE_TWO"); - ProviderManager mgr = makeProviderManagerWithMockProviderWhichReturnsNullInList(); - mgr.setApplicationEventPublisher(new MockApplicationEventPublisher(true)); + public void authenticationSucceedsWhenFirstProviderReturnsNullButSecondAuthenticates() { + final Authentication a = mock(Authentication.class); + ProviderManager mgr = new ProviderManager(); + AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class); + mgr.setAuthenticationEventPublisher(publisher); + mgr.setProviders(Arrays.asList(createProviderWhichReturns(null), createProviderWhichReturns(a))); - Authentication result = mgr.authenticate(token); - - if (!(result instanceof TestingAuthenticationToken)) { - fail("Should have returned instance of TestingAuthenticationToken"); - } - - TestingAuthenticationToken castResult = (TestingAuthenticationToken) result; - assertEquals("Test", castResult.getPrincipal()); - assertEquals("Password", castResult.getCredentials()); - assertEquals("ROLE_ONE", castResult.getAuthorities().get(0).getAuthority()); - assertEquals("ROLE_TWO", castResult.getAuthorities().get(1).getAuthority()); + Authentication result = mgr.authenticate(a); + assertSame(a, result); + verify(publisher).publishAuthenticationSuccess(result); } @Test @@ -99,11 +87,10 @@ public class ProviderManagerTests { //The NullConcurrentSessionController should be the default assertNotNull(target.getSessionController()); - assertTrue(target.getSessionController() instanceof NullConcurrentSessionController); ConcurrentSessionController csc = mock(ConcurrentSessionController.class); target.setSessionController(csc); - assertEquals(csc, target.getSessionController()); + assertSame(csc, target.getSessionController()); } @Test(expected=IllegalArgumentException.class) @@ -119,8 +106,6 @@ public class ProviderManagerTests { @Test(expected=IllegalArgumentException.class) public void getProvidersFailsIfProviderListNotSet() throws Exception { ProviderManager mgr = new ProviderManager(); - - mgr.afterPropertiesSet(); mgr.getProviders(); } @@ -137,6 +122,7 @@ public class ProviderManagerTests { final Object resultDetails = "(Result Details)"; ProviderManager authMgr = makeProviderManager(); + // A provider which sets the details object AuthenticationProvider provider = new AuthenticationProvider() { public Authentication authenticate(Authentication authentication) throws AuthenticationException { ((TestingAuthenticationToken)authentication).setDetails(resultDetails); @@ -166,25 +152,53 @@ public class ProviderManagerTests { request.setDetails(details); Authentication result = authMgr.authenticate(request); - assertEquals(details, result.getDetails()); + assertSame(details, result.getDetails()); + } + + @Test + public void authenticationExceptionIsIgnoredIfLaterProviderAuthenticates() throws Exception { + ProviderManager mgr = new ProviderManager(); + final Authentication authReq = mock(Authentication.class); + mgr.setProviders(Arrays.asList(createProviderWhichThrows(new BadCredentialsException("")), + createProviderWhichReturns(authReq))); + assertSame(authReq, mgr.authenticate(mock(Authentication.class))); + } + + @Test + public void authenticationExceptionIsRethrownIfNoLaterProviderAuthenticates() throws Exception { + ProviderManager mgr = new ProviderManager(); + + mgr.setProviders(Arrays.asList(createProviderWhichThrows(new BadCredentialsException("")), + createProviderWhichReturns(null))); + try { + mgr.authenticate(mock(Authentication.class)); + fail("Expected BadCredentialsException"); + } catch (BadCredentialsException expected) { + } } // SEC-546 - @Test(expected=AccountStatusException.class) + @Test public void accountStatusExceptionPreventsCallsToSubsequentProviders() throws Exception { ProviderManager authMgr = makeProviderManager(); + AuthenticationProvider iThrowAccountStatusException = createProviderWhichThrows(new AccountStatusException(""){}); + AuthenticationProvider otherProvider = mock(AuthenticationProvider.class); - authMgr.setProviders(Arrays.asList(new MockProviderWhichThrowsAccountStatusException(), - new MockProviderWhichThrowsConcurrentLoginException()) ); + authMgr.setProviders(Arrays.asList(iThrowAccountStatusException, otherProvider)); - authMgr.authenticate(createAuthenticationToken()); + try { + authMgr.authenticate(mock(Authentication.class)); + fail("Expected AccountStatusException"); + } catch (AccountStatusException expected) { + } + verifyZeroInteractions(otherProvider); } @Test(expected=ConcurrentLoginException.class) public void concurrentLoginExceptionPreventsCallsToSubsequentProviders() throws Exception { ProviderManager authMgr = makeProviderManager(); - // Two providers so if the second is polled it will throw an AccountStatusException - authMgr.setProviders(Arrays.asList(new MockProvider(), new MockProviderWhichThrowsAccountStatusException()) ); + // Two providers so if the second is polled it will throw an BadCredentialsException + authMgr.setProviders(Arrays.asList(new MockProvider(), createProviderWhichThrows(new BadCredentialsException(""))) ); TestingAuthenticationToken request = createAuthenticationToken(); ConcurrentSessionController ctrlr = mock(ConcurrentSessionController.class); doThrow(new ConcurrentLoginException("mocked")).when(ctrlr).checkAuthenticationAllowed(request); @@ -193,6 +207,88 @@ public class ProviderManagerTests { authMgr.authenticate(request); } + @Test + public void parentAuthenticationIsUsedIfProvidersDontAuthenticate() throws Exception { + ProviderManager mgr = new ProviderManager(); + mgr.setProviders(Arrays.asList(mock(AuthenticationProvider.class))); + Authentication authReq = mock(Authentication.class); + AuthenticationManager parent = mock(AuthenticationManager.class); + when(parent.authenticate(authReq)).thenReturn(authReq); + mgr.setParent(parent); + assertSame(authReq, mgr.authenticate(authReq)); + } + + @Test + public void parentIsNotCalledIfAccountStatusExceptionIsThrown() throws Exception { + ProviderManager mgr = new ProviderManager(); + AuthenticationProvider iThrowAccountStatusException = createProviderWhichThrows(new AccountStatusException(""){}); + mgr.setProviders(Arrays.asList(iThrowAccountStatusException)); + AuthenticationManager parent = mock(AuthenticationManager.class); + mgr.setParent(parent); + try { + mgr.authenticate(mock(Authentication.class)); + fail("Expected exception"); + } catch (AccountStatusException expected) { + } + verifyZeroInteractions(parent); + } + + @Test + public void providerNotFoundFromParentIsIgnored() throws Exception { + ProviderManager mgr = new ProviderManager(); + final Authentication authReq = mock(Authentication.class); + AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class); + mgr.setAuthenticationEventPublisher(publisher); + // Set a provider that throws an exception - this is the exception we expect to be propagated + mgr.setProviders(Arrays.asList(createProviderWhichThrows(new BadCredentialsException("")))); + AuthenticationManager parent = mock(AuthenticationManager.class); + when(parent.authenticate(authReq)).thenThrow(new ProviderNotFoundException("")); + mgr.setParent(parent); + try { + mgr.authenticate(authReq); + fail("Expected exception"); + } catch (BadCredentialsException expected) { + verify(publisher).publishAuthenticationFailure(expected, authReq); + } + } + + @Test + public void authenticationExceptionFromParentOverridesPreviousOnes() throws Exception { + ProviderManager mgr = new ProviderManager(); + final Authentication authReq = mock(Authentication.class); + AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class); + mgr.setAuthenticationEventPublisher(publisher); + // Set a provider that throws an exception - this is the exception we expect to be propagated + final BadCredentialsException expected = new BadCredentialsException("I'm the one from the parent"); + mgr.setProviders(Arrays.asList(createProviderWhichThrows(new BadCredentialsException("")))); + AuthenticationManager parent = mock(AuthenticationManager.class); + when(parent.authenticate(authReq)).thenThrow(expected); + mgr.setParent(parent); + try { + mgr.authenticate(authReq); + fail("Expected exception"); + } catch (BadCredentialsException e) { + assertSame(expected, e); + } + verify(publisher).publishAuthenticationFailure(expected, authReq); + } + + private AuthenticationProvider createProviderWhichThrows(final AuthenticationException e) { + AuthenticationProvider provider = mock(AuthenticationProvider.class); + when(provider.supports(any(Class.class))).thenReturn(true); + when(provider.authenticate(any(Authentication.class))).thenThrow(e); + + return provider; + } + + private AuthenticationProvider createProviderWhichReturns(final Authentication a) { + AuthenticationProvider provider = mock(AuthenticationProvider.class); + when(provider.supports(any(Class.class))).thenReturn(true); + when(provider.authenticate(any(Authentication.class))).thenReturn(a); + + return provider; + } + private TestingAuthenticationToken createAuthenticationToken() { return new TestingAuthenticationToken("name", "password", new ArrayList(0)); } @@ -205,21 +301,6 @@ public class ProviderManagerTests { ProviderManager mgr = new ProviderManager(); mgr.setProviders(providers); - mgr.afterPropertiesSet(); - - return mgr; - } - - private ProviderManager makeProviderManagerWithMockProviderWhichReturnsNullInList() { - MockProviderWhichReturnsNull provider1 = new MockProviderWhichReturnsNull(); - MockProvider provider2 = new MockProvider(); - List providers = new ArrayList(); - providers.add(provider1); - providers.add(provider2); - - ProviderManager mgr = new ProviderManager(); - mgr.setProviders(providers); - return mgr; } @@ -242,43 +323,4 @@ public class ProviderManagerTests { } } } - - private class MockProviderWhichReturnsNull implements AuthenticationProvider { - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - if (supports(authentication.getClass())) { - return null; - } else { - throw new AuthenticationServiceException("Don't support this class"); - } - } - - public boolean supports(Class authentication) { - if (TestingAuthenticationToken.class.isAssignableFrom(authentication)) { - return true; - } else { - return false; - } - } - } - - private class MockProviderWhichThrowsAccountStatusException implements AuthenticationProvider { - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - throw new AccountStatusException("xxx") {}; - } - - public boolean supports(Class authentication) { - return true; - } - } - - private class MockProviderWhichThrowsConcurrentLoginException implements AuthenticationProvider { - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - throw new ConcurrentLoginException("xxx") {}; - } - - public boolean supports(Class authentication) { - return true; - } - } - } diff --git a/core/src/test/java/org/springframework/security/util/MethodInvocationUtilsTests.java b/core/src/test/java/org/springframework/security/util/MethodInvocationUtilsTests.java new file mode 100644 index 0000000000..1b17a36edf --- /dev/null +++ b/core/src/test/java/org/springframework/security/util/MethodInvocationUtilsTests.java @@ -0,0 +1,34 @@ +package org.springframework.security.util; + +import static org.junit.Assert.*; + +import org.aopalliance.intercept.MethodInvocation; +import org.junit.Test; + +/** + * + * @author Luke Taylor + * @version $Id$ + */ +public class MethodInvocationUtilsTests { + + @Test + public void createFromClassReturnsMethodWithNoArgInfoForMethodWithNoArgs() { + MethodInvocation mi = MethodInvocationUtils.createFromClass(String.class, "length"); + assertNotNull(mi); + } + + @Test + public void createFromClassWithNoArgInfoReturnsNullForMethodWithArgs() { + MethodInvocation mi = MethodInvocationUtils.createFromClass(String.class, "codePointAt"); + assertNull(mi); + } + + @Test + public void createFromClassReturnsMethodIfGivArgInfoForMethodWithArgs() { + MethodInvocation mi = MethodInvocationUtils.createFromClass(null, String.class, "compareTo", + new Class[]{String.class}, new Object[] {""}); + assertNotNull(mi); + } + +}