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: 295   Methods: 10
NCLOC: 121   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
AbstractUserDetailsAuthenticationProvider.java 100% 100% 90% 98.5%
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.dao;
 17   
 18    import org.acegisecurity.AccountExpiredException;
 19    import org.acegisecurity.AcegiMessageSource;
 20    import org.acegisecurity.Authentication;
 21    import org.acegisecurity.AuthenticationException;
 22    import org.acegisecurity.CredentialsExpiredException;
 23    import org.acegisecurity.DisabledException;
 24    import org.acegisecurity.LockedException;
 25    import org.acegisecurity.providers.AuthenticationProvider;
 26    import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 27    import org.acegisecurity.providers.dao.cache.NullUserCache;
 28    import org.acegisecurity.userdetails.UserDetails;
 29    import org.acegisecurity.userdetails.UserDetailsService;
 30    import org.springframework.beans.factory.InitializingBean;
 31    import org.springframework.context.MessageSource;
 32    import org.springframework.context.MessageSourceAware;
 33    import org.springframework.context.support.MessageSourceAccessor;
 34    import org.springframework.util.Assert;
 35   
 36   
 37    /**
 38    * A base {@link AuthenticationProvider} that allows subclasses to override and
 39    * work with {@link org.acegisecurity.userdetails.UserDetails} objects. The class is
 40    * designed to respond to {@link UsernamePasswordAuthenticationToken}
 41    * authentication requests.
 42    *
 43    * <p>
 44    * Upon successful validation, a
 45    * <code>UsernamePasswordAuthenticationToken</code> will be created and
 46    * returned to the caller. The token will include as its principal either a
 47    * <code>String</code> representation of the username, or the {@link
 48    * UserDetails} that was returned from the authentication repository. Using
 49    * <code>String</code> is appropriate if a container adapter is being used, as
 50    * it expects <code>String</code> representations of the username. Using
 51    * <code>UserDetails</code> is appropriate if you require access to additional
 52    * properties of the authenticated user, such as email addresses,
 53    * human-friendly names etc. As container adapters are not recommended to be
 54    * used, and <code>UserDetails</code> implementations provide additional
 55    * flexibility, by default a <code>UserDetails</code> is returned. To override
 56    * this default, set the {@link #setForcePrincipalAsString} to
 57    * <code>true</code>.
 58    * </p>
 59    *
 60    * <P>
 61    * Caching is handled via the <code>UserDetails</code> object being placed in
 62    * the {@link UserCache}. This ensures that subsequent requests with the same
 63    * username can be validated without needing to query the {@link
 64    * UserDetailsService}. It should be noted that if a user appears to present an
 65    * incorrect password, the {@link UserDetailsService} will be queried to
 66    * confirm the most up-to-date password was used for comparison.
 67    * </p>
 68    */
 69    public abstract class AbstractUserDetailsAuthenticationProvider
 70    implements AuthenticationProvider, InitializingBean, MessageSourceAware {
 71    //~ Instance fields ========================================================
 72   
 73    protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
 74    private UserCache userCache = new NullUserCache();
 75    private boolean forcePrincipalAsString = false;
 76   
 77    //~ Methods ================================================================
 78   
 79    /**
 80    * Allows subclasses to perform any additional checks of a returned (or
 81    * cached) <code>UserDetails</code> for a given authentication request.
 82    * Generally a subclass will at least compare the {@link
 83    * Authentication#getCredentials()} with a {@link
 84    * UserDetails#getPassword()}. If custom logic is needed to compare
 85    * additional properties of <code>UserDetails</code> and/or
 86    * <code>UsernamePasswordAuthenticationToken</code>, these should also
 87    * appear in this method.
 88    *
 89    * @param userDetails as retrieved from the {@link #retrieveUser(String,
 90    * UsernamePasswordAuthenticationToken)} or <code>UserCache</code>
 91    * @param authentication the current request that needs to be authenticated
 92    *
 93    * @throws AuthenticationException AuthenticationException if the
 94    * credentials could not be validated (generally a
 95    * <code>BadCredentialsException</code>, an
 96    * <code>AuthenticationServiceException</code>)
 97    */
 98    protected abstract void additionalAuthenticationChecks(
 99    UserDetails userDetails,
 100    UsernamePasswordAuthenticationToken authentication)
 101    throws AuthenticationException;
 102   
 103  37 public final void afterPropertiesSet() throws Exception {
 104  37 Assert.notNull(this.userCache, "A user cache must be set");
 105  36 Assert.notNull(this.messages, "A message source must be set");
 106  36 doAfterPropertiesSet();
 107    }
 108   
 109  25 public Authentication authenticate(Authentication authentication)
 110    throws AuthenticationException {
 111  25 Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class,
 112    authentication,
 113    messages.getMessage(
 114    "AbstractUserDetailsAuthenticationProvider.onlySupports",
 115    "Only UsernamePasswordAuthenticationToken is supported"));
 116   
 117    // Determine username
 118  25 String username = (authentication.getPrincipal() == null)
 119    ? "NONE_PROVIDED" : authentication.getName();
 120   
 121  25 boolean cacheWasUsed = true;
 122  25 UserDetails user = this.userCache.getUserFromCache(username);
 123   
 124  25 if (user == null) {
 125  23 cacheWasUsed = false;
 126  23 user = retrieveUser(username,
 127    (UsernamePasswordAuthenticationToken) authentication);
 128  15 Assert.notNull(user,
 129    "retrieveUser returned null - a violation of the interface contract");
 130    }
 131   
 132  17 if (!user.isAccountNonLocked()) {
 133  1 throw new LockedException(messages.getMessage(
 134    "AbstractUserDetailsAuthenticationProvider.locked",
 135    "User account is locked"));
 136    }
 137   
 138  16 if (!user.isEnabled()) {
 139  1 throw new DisabledException(messages.getMessage(
 140    "AbstractUserDetailsAuthenticationProvider.disabled",
 141    "User is disabled"));
 142    }
 143   
 144  15 if (!user.isAccountNonExpired()) {
 145  1 throw new AccountExpiredException(messages.getMessage(
 146    "AbstractUserDetailsAuthenticationProvider.expired",
 147    "User account has expired"));
 148    }
 149   
 150    // This check must come here, as we don't want to tell users
 151    // about account status unless they presented the correct credentials
 152  14 try {
 153  14 additionalAuthenticationChecks(user,
 154    (UsernamePasswordAuthenticationToken) authentication);
 155    } catch (AuthenticationException exception) {
 156    // There was a problem, so try again after checking we're using latest data
 157  5 cacheWasUsed = false;
 158  5 user = retrieveUser(username,
 159    (UsernamePasswordAuthenticationToken) authentication);
 160  5 additionalAuthenticationChecks(user,
 161    (UsernamePasswordAuthenticationToken) authentication);
 162    }
 163   
 164  10 if (!user.isCredentialsNonExpired()) {
 165  1 throw new CredentialsExpiredException(messages.getMessage(
 166    "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
 167    "User credentials have expired"));
 168    }
 169   
 170  9 if (!cacheWasUsed) {
 171  8 this.userCache.putUserInCache(user);
 172    }
 173   
 174  9 Object principalToReturn = user;
 175   
 176  9 if (forcePrincipalAsString) {
 177  1 principalToReturn = user.getUsername();
 178    }
 179   
 180  9 return createSuccessAuthentication(principalToReturn, authentication,
 181    user);
 182    }
 183   
 184    /**
 185    * Creates a successful {@link Authentication} object.
 186    *
 187    * <P>
 188    * Protected so subclasses can override.
 189    * </p>
 190    *
 191    * <P>
 192    * Subclasses will usually store the original credentials the user supplied
 193    * (not salted or encoded passwords) in the returned
 194    * <code>Authentication</code> object.
 195    * </p>
 196    *
 197    * @param principal that should be the principal in the returned object
 198    * (defined by the {@link #isForcePrincipalAsString()} method)
 199    * @param authentication that was presented to the
 200    * <code>DaoAuthenticationProvider</code> for validation
 201    * @param user that was loaded by the <code>AuthenticationDao</code>
 202    *
 203    * @return the successful authentication token
 204    */
 205  9 protected Authentication createSuccessAuthentication(Object principal,
 206    Authentication authentication, UserDetails user) {
 207    // Ensure we return the original credentials the user supplied,
 208    // so subsequent attempts are successful even with encoded passwords.
 209    // Also ensure we return the original getDetails(), so that future
 210    // authentication events after cache expiry contain the details
 211  9 UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
 212    authentication.getCredentials(), user.getAuthorities());
 213  9 result.setDetails((authentication.getDetails() != null)
 214    ? authentication.getDetails() : null);
 215   
 216  9 return result;
 217    }
 218   
 219  0 protected void doAfterPropertiesSet() throws Exception {}
 220   
 221  2 public UserCache getUserCache() {
 222  2 return userCache;
 223    }
 224   
 225  2 public boolean isForcePrincipalAsString() {
 226  2 return forcePrincipalAsString;
 227    }
 228   
 229    /**
 230    * Allows subclasses to actually retrieve the <code>UserDetails</code> from
 231    * an implementation-specific location, with the option of throwing an
 232    * <code>AuthenticationException</code> immediately if the presented
 233    * credentials are incorrect (this is especially useful if it is necessary
 234    * to bind to a resource as the user in order to obtain or generate a
 235    * <code>UserDetails</code>).
 236    *
 237    * <p>
 238    * Subclasses are not required to perform any caching, as the
 239    * <code>AbstractUserDetailsAuthenticationProvider</code> will by default
 240    * cache the <code>UserDetails</code>. The caching of
 241    * <code>UserDetails</code> does present additional complexity as this
 242    * means subsequent requests that rely on the cache will need to still
 243    * have their credentials validated, even if the correctness of
 244    * credentials was assured by subclasses adopting a binding-based strategy
 245    * in this method. Accordingly it is important that subclasses either
 246    * disable caching (if they want to ensure that this method is the only
 247    * method that is capable of authenticating a request, as no
 248    * <code>UserDetails</code> will ever be cached) or ensure subclasses
 249    * implement {@link #additionalAuthenticationChecks(UserDetails,
 250    * UsernamePasswordAuthenticationToken)} to compare the credentials of a
 251    * cached <code>UserDetails</code> with subsequent authentication
 252    * requests.
 253    * </p>
 254    *
 255    * <p>
 256    * Most of the time subclasses will not perform credentials inspection in
 257    * this method, instead performing it in {@link
 258    * #additionalAuthenticationChecks(UserDetails,
 259    * UsernamePasswordAuthenticationToken)} so that code related to
 260    * credentials validation need not be duplicated across two methods.
 261    * </p>
 262    *
 263    * @param username The username to retrieve
 264    * @param authentication The authentication request, which subclasses
 265    * <em>may</em> need to perform a binding-based retrieval of the
 266    * <code>UserDetails</code>
 267    *
 268    * @return the user information (never <code>null</code> - instead an
 269    * exception should the thrown)
 270    *
 271    * @throws AuthenticationException if the credentials could not be
 272    * validated (generally a <code>BadCredentialsException</code>, an
 273    * <code>AuthenticationServiceException</code> or
 274    * <code>UserNotFoundException</code>)
 275    */
 276    protected abstract UserDetails retrieveUser(String username,
 277    UsernamePasswordAuthenticationToken authentication)
 278    throws AuthenticationException;
 279   
 280  2 public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
 281  2 this.forcePrincipalAsString = forcePrincipalAsString;
 282    }
 283   
 284  34 public void setMessageSource(MessageSource messageSource) {
 285  34 this.messages = new MessageSourceAccessor(messageSource);
 286    }
 287   
 288  19 public void setUserCache(UserCache userCache) {
 289  19 this.userCache = userCache;
 290    }
 291   
 292  7 public boolean supports(Class authentication) {
 293  7 return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
 294    }
 295    }