Clover coverage report - Acegi Security System for Spring - 1.0.0-RC1
Coverage timestamp: Mon Dec 5 2005 09:05:15 EST
file stats: LOC: 330   Methods: 10
NCLOC: 179   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ProviderManager.java 80% 96.9% 100% 93.6%
coverage coverage
 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;
 17   
 18    import java.lang.reflect.Constructor;
 19    import java.lang.reflect.InvocationTargetException;
 20    import java.util.Iterator;
 21    import java.util.List;
 22    import java.util.Properties;
 23   
 24    import org.acegisecurity.AbstractAuthenticationManager;
 25    import org.acegisecurity.AccountExpiredException;
 26    import org.acegisecurity.AcegiMessageSource;
 27    import org.acegisecurity.Authentication;
 28    import org.acegisecurity.AuthenticationException;
 29    import org.acegisecurity.AuthenticationServiceException;
 30    import org.acegisecurity.BadCredentialsException;
 31    import org.acegisecurity.CredentialsExpiredException;
 32    import org.acegisecurity.DisabledException;
 33    import org.acegisecurity.LockedException;
 34    import org.acegisecurity.concurrent.ConcurrentLoginException;
 35    import org.acegisecurity.concurrent.ConcurrentSessionController;
 36    import org.acegisecurity.concurrent.NullConcurrentSessionController;
 37    import org.acegisecurity.event.authentication.AbstractAuthenticationEvent;
 38    import org.acegisecurity.event.authentication.AuthenticationFailureBadCredentialsEvent;
 39    import org.acegisecurity.event.authentication.AuthenticationFailureConcurrentLoginEvent;
 40    import org.acegisecurity.event.authentication.AuthenticationFailureCredentialsExpiredEvent;
 41    import org.acegisecurity.event.authentication.AuthenticationFailureDisabledEvent;
 42    import org.acegisecurity.event.authentication.AuthenticationFailureExpiredEvent;
 43    import org.acegisecurity.event.authentication.AuthenticationFailureLockedEvent;
 44    import org.acegisecurity.event.authentication.AuthenticationFailureProviderNotFoundEvent;
 45    import org.acegisecurity.event.authentication.AuthenticationFailureProxyUntrustedEvent;
 46    import org.acegisecurity.event.authentication.AuthenticationFailureServiceExceptionEvent;
 47    import org.acegisecurity.event.authentication.AuthenticationSuccessEvent;
 48    import org.acegisecurity.providers.cas.ProxyUntrustedException;
 49    import org.acegisecurity.userdetails.UsernameNotFoundException;
 50    import org.apache.commons.logging.Log;
 51    import org.apache.commons.logging.LogFactory;
 52    import org.springframework.beans.factory.InitializingBean;
 53    import org.springframework.context.ApplicationEventPublisher;
 54    import org.springframework.context.ApplicationEventPublisherAware;
 55    import org.springframework.context.MessageSource;
 56    import org.springframework.context.MessageSourceAware;
 57    import org.springframework.context.support.MessageSourceAccessor;
 58    import org.springframework.util.Assert;
 59   
 60   
 61    /**
 62    * Iterates an {@link Authentication} request through a list of {@link
 63    * AuthenticationProvider}s. Can optionally be configured with a {@link
 64    * ConcurrentSessionController} to limit the number of sessions a user can
 65    * have.
 66    *
 67    * <p>
 68    * <code>AuthenticationProvider</code>s are tried in order until one provides a
 69    * non-null response. A non-null response indicates the provider had authority
 70    * to decide on the authentication request and no further providers are tried.
 71    * If an <code>AuthenticationException</code> is thrown by a provider, it is
 72    * retained until subsequent providers are tried. If a subsequent provider
 73    * successfully authenticates the request, the earlier authentication
 74    * exception is disregarded and the successful authentication will be used. If
 75    * no subsequent provider provides a non-null response, or a new
 76    * <code>AuthenticationException</code>, the last
 77    * <code>AuthenticationException</code> received will be used. If no provider
 78    * returns a non-null response, or indicates it can even process an
 79    * <code>Authentication</code>, the <code>ProviderManager</code> will throw a
 80    * <code>ProviderNotFoundException</code>.
 81    * </p>
 82    *
 83    * <p>
 84    * If a valid <code>Authentication</code> is returned by an
 85    * <code>AuthenticationProvider</code>, the <code>ProviderManager</code> will
 86    * publish an {@link
 87    * org.acegisecurity.event.authentication.AuthenticationSuccessEvent}. If an
 88    * <code>AuthenticationException</code> is detected, the final
 89    * <code>AuthenticationException</code> thrown will be used to publish an
 90    * appropriate failure event. By default <code>ProviderManager</code> maps
 91    * common exceptions to events, but this can be fine-tuned by providing a new
 92    * <code>exceptionMappings</code><code>java.util.Properties</code> object. In
 93    * the properties object, each of the keys represent the fully qualified
 94    * classname of the exception, and each of the values represent the name of an
 95    * event class which subclasses {@link
 96    * org.acegisecurity.event.authentication.AbstractAuthenticationFailureEvent}
 97    * and provides its constructor.
 98    * </p>
 99    *
 100    * @see ConcurrentSessionController
 101    */
 102    public class ProviderManager extends AbstractAuthenticationManager
 103    implements InitializingBean, ApplicationEventPublisherAware,
 104    MessageSourceAware {
 105    //~ Static fields/initializers =============================================
 106   
 107    private static final Log logger = LogFactory.getLog(ProviderManager.class);
 108   
 109    //~ Instance fields ========================================================
 110   
 111    private ApplicationEventPublisher applicationEventPublisher;
 112    private ConcurrentSessionController sessionController = new NullConcurrentSessionController();
 113    private List providers;
 114    protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
 115    private Properties exceptionMappings;
 116   
 117    //~ Methods ================================================================
 118   
 119  39 public void afterPropertiesSet() throws Exception {
 120  39 checkIfValidList(this.providers);
 121  38 Assert.notNull(this.messages, "A message source must be set");
 122   
 123  38 if (exceptionMappings == null) {
 124  37 exceptionMappings = new Properties();
 125  37 exceptionMappings.put(AccountExpiredException.class.getName(),
 126    AuthenticationFailureExpiredEvent.class.getName());
 127  37 exceptionMappings.put(AuthenticationServiceException.class.getName(),
 128    AuthenticationFailureServiceExceptionEvent.class.getName());
 129  37 exceptionMappings.put(LockedException.class.getName(),
 130    AuthenticationFailureLockedEvent.class.getName());
 131  37 exceptionMappings.put(CredentialsExpiredException.class.getName(),
 132    AuthenticationFailureCredentialsExpiredEvent.class.getName());
 133  37 exceptionMappings.put(DisabledException.class.getName(),
 134    AuthenticationFailureDisabledEvent.class.getName());
 135  37 exceptionMappings.put(BadCredentialsException.class.getName(),
 136    AuthenticationFailureBadCredentialsEvent.class.getName());
 137  37 exceptionMappings.put(UsernameNotFoundException.class.getName(),
 138    AuthenticationFailureBadCredentialsEvent.class.getName());
 139  37 exceptionMappings.put(ConcurrentLoginException.class.getName(),
 140    AuthenticationFailureConcurrentLoginEvent.class.getName());
 141  37 exceptionMappings.put(ProviderNotFoundException.class.getName(),
 142    AuthenticationFailureProviderNotFoundEvent.class.getName());
 143  37 exceptionMappings.put(ProxyUntrustedException.class.getName(),
 144    AuthenticationFailureProxyUntrustedEvent.class.getName());
 145  37 doAddExtraDefaultExceptionMappings(exceptionMappings);
 146    }
 147    }
 148   
 149  79 private void checkIfValidList(List listToCheck) {
 150  79 if ((listToCheck == null) || (listToCheck.size() == 0)) {
 151  2 throw new IllegalArgumentException(
 152    "A list of AuthenticationManagers is required");
 153    }
 154    }
 155   
 156    /**
 157    * Provided so subclasses can add extra exception mappings during startup
 158    * if no exception mappings are injected by the IoC container.
 159    *
 160    * @param exceptionMappings the properties object, which already has
 161    * entries in it
 162    */
 163  37 protected void doAddExtraDefaultExceptionMappings(
 164    Properties exceptionMappings) {}
 165   
 166    /**
 167    * Attempts to authenticate the passed {@link Authentication} object.
 168    *
 169    * <p>
 170    * The list of {@link AuthenticationProvider}s will be successively tried
 171    * until an <code>AuthenticationProvider</code> indicates it is capable
 172    * of authenticating the type of <code>Authentication</code> object
 173    * passed. Authentication will then be attempted with that
 174    * <code>AuthenticationProvider</code>.
 175    * </p>
 176    *
 177    * <p>
 178    * If more than one <code>AuthenticationProvider</code> supports the passed
 179    * <code>Authentication</code> object, only the first
 180    * <code>AuthenticationProvider</code> tried will determine the result. No
 181    * subsequent <code>AuthenticationProvider</code>s will be tried.
 182    * </p>
 183    *
 184    * @param authentication the authentication request object.
 185    *
 186    * @return a fully authenticated object including credentials.
 187    *
 188    * @throws AuthenticationException if authentication fails.
 189    */
 190  8 public Authentication doAuthentication(Authentication authentication)
 191    throws AuthenticationException {
 192  8 Iterator iter = providers.iterator();
 193   
 194  8 Class toTest = authentication.getClass();
 195   
 196  8 AuthenticationException lastException = null;
 197   
 198  8 while (iter.hasNext()) {
 199  9 AuthenticationProvider provider = (AuthenticationProvider) iter.next();
 200   
 201  9 if (provider.supports(toTest)) {
 202  8 logger.debug("Authentication attempt using "
 203    + provider.getClass().getName());
 204   
 205  8 Authentication result = null;
 206   
 207  8 try {
 208  8 result = provider.authenticate(authentication);
 209  5 sessionController.checkAuthenticationAllowed(result);
 210    } catch (AuthenticationException ae) {
 211  3 lastException = ae;
 212  3 result = null;
 213    }
 214   
 215  8 if (result != null) {
 216  4 sessionController.registerSuccessfulAuthentication(result);
 217  4 applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(
 218    result));
 219   
 220  4 return result;
 221    }
 222    }
 223    }
 224   
 225  4 if (lastException == null) {
 226  1 lastException = new ProviderNotFoundException(messages.getMessage(
 227    "ProviderManager.providerNotFound",
 228    new Object[] {toTest.getName()},
 229    "No AuthenticationProvider found for {0}"));
 230    }
 231   
 232    // Publish the event
 233  4 String className = exceptionMappings.getProperty(lastException.getClass()
 234    .getName());
 235  4 AbstractAuthenticationEvent event = null;
 236   
 237  4 if (className != null) {
 238  4 try {
 239  4 Class clazz = getClass().getClassLoader().loadClass(className);
 240  4 Constructor constructor = clazz.getConstructor(new Class[] {Authentication.class, AuthenticationException.class});
 241  4 Object obj = constructor.newInstance(new Object[] {authentication, lastException});
 242  4 Assert.isInstanceOf(AbstractAuthenticationEvent.class, obj,
 243    "Must be an AbstractAuthenticationEvent");
 244  4 event = (AbstractAuthenticationEvent) obj;
 245    } catch (ClassNotFoundException ignored) {}
 246    catch (NoSuchMethodException ignored) {}
 247    catch (IllegalAccessException ignored) {}
 248    catch (InstantiationException ignored) {}
 249    catch (InvocationTargetException ignored) {}
 250    }
 251   
 252  4 if (event != null) {
 253  4 applicationEventPublisher.publishEvent(event);
 254    } else {
 255  0 if (logger.isDebugEnabled()) {
 256  0 logger.debug("No event was found for the exception "
 257    + lastException.getClass().getName());
 258    }
 259    }
 260   
 261    // Throw the exception
 262  4 throw lastException;
 263    }
 264   
 265  1 public List getProviders() {
 266  1 return this.providers;
 267    }
 268   
 269    /**
 270    * The configured {@link ConcurrentSessionController} is returned or the
 271    * {@link NullConcurrentSessionController} if a specific one has not been
 272    * set.
 273    *
 274    * @return {@link ConcurrentSessionController} instance
 275    */
 276  3 public ConcurrentSessionController getSessionController() {
 277  3 return sessionController;
 278    }
 279   
 280  37 public void setApplicationEventPublisher(
 281    ApplicationEventPublisher applicationEventPublisher) {
 282  37 this.applicationEventPublisher = applicationEventPublisher;
 283    }
 284   
 285  34 public void setMessageSource(MessageSource messageSource) {
 286  34 this.messages = new MessageSourceAccessor(messageSource);
 287    }
 288   
 289    /**
 290    * Sets the {@link AuthenticationProvider} objects to be used for
 291    * authentication.
 292    *
 293    * @param newList
 294    *
 295    * @throws IllegalArgumentException DOCUMENT ME!
 296    */
 297  40 public void setProviders(List newList) {
 298  40 checkIfValidList(newList);
 299   
 300  39 Iterator iter = newList.iterator();
 301   
 302  39 while (iter.hasNext()) {
 303  40 Object currentObject = null;
 304   
 305  40 try {
 306  40 currentObject = iter.next();
 307   
 308  40 AuthenticationProvider attemptToCast = (AuthenticationProvider) currentObject;
 309    } catch (ClassCastException cce) {
 310  1 throw new IllegalArgumentException("AuthenticationProvider "
 311    + currentObject.getClass().getName()
 312    + " must implement AuthenticationProvider");
 313    }
 314    }
 315   
 316  38 this.providers = newList;
 317    }
 318   
 319    /**
 320    * Set the {@link ConcurrentSessionController} to be used for limiting
 321    * user's sessions. The {@link NullConcurrentSessionController} is used
 322    * by default
 323    *
 324    * @param sessionController {@link ConcurrentSessionController}
 325    */
 326  1 public void setSessionController(
 327    ConcurrentSessionController sessionController) {
 328  1 this.sessionController = sessionController;
 329    }
 330    }