Browse Source

SEC-56: Add localisation support.

Ben Alex 20 years ago
parent
commit
fddcd6112e
44 changed files with 2346 additions and 1805 deletions
  1. 324 319
      core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImpl.java
  2. 27 16
      core/src/main/java/org/acegisecurity/adapters/AuthByAdapterProvider.java
  3. 57 48
      core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProvider.java
  4. 116 90
      core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImpl.java
  5. 311 296
      core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java
  6. 39 0
      core/src/main/java/org/acegisecurity/messages.properties
  7. 93 84
      core/src/main/java/org/acegisecurity/providers/ProviderManager.java
  8. 24 15
      core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProvider.java
  9. 80 65
      core/src/main/java/org/acegisecurity/providers/cas/CasAuthenticationProvider.java
  10. 26 15
      core/src/main/java/org/acegisecurity/providers/cas/proxy/NamedCasProxyDecider.java
  11. 25 7
      core/src/main/java/org/acegisecurity/providers/cas/proxy/RejectProxyTickets.java
  12. 97 80
      core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java
  13. 77 73
      core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java
  14. 23 14
      core/src/main/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProvider.java
  15. 72 45
      core/src/main/java/org/acegisecurity/providers/x509/X509AuthenticationProvider.java
  16. 62 43
      core/src/main/java/org/acegisecurity/providers/x509/populator/DaoX509AuthoritiesPopulator.java
  17. 26 16
      core/src/main/java/org/acegisecurity/runas/RunAsImplAuthenticationProvider.java
  18. 126 113
      core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java
  19. 118 84
      core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java
  20. 405 316
      core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilter.java
  21. 30 21
      core/src/main/java/org/acegisecurity/vote/AbstractAccessDecisionManager.java
  22. 7 6
      core/src/main/java/org/acegisecurity/vote/AffirmativeBased.java
  23. 19 16
      core/src/main/java/org/acegisecurity/vote/ConsensusBased.java
  24. 7 6
      core/src/main/java/org/acegisecurity/vote/UnanimousBased.java
  25. 4 0
      core/src/test/java/org/acegisecurity/adapters/AuthByAdapterTests.java
  26. 10 0
      core/src/test/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProviderTests.java
  27. 2 0
      core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImplTests.java
  28. 13 0
      core/src/test/java/org/acegisecurity/intercept/method/aopalliance/MethodSecurityInterceptorTests.java
  29. 3 0
      core/src/test/java/org/acegisecurity/intercept/method/aspectj/AspectJSecurityInterceptorTests.java
  30. 6 0
      core/src/test/java/org/acegisecurity/intercept/web/FilterSecurityInterceptorTests.java
  31. 2 1
      core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
  32. 7 0
      core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProviderTests.java
  33. 14 0
      core/src/test/java/org/acegisecurity/providers/cas/CasAuthenticationProviderTests.java
  34. 14 8
      core/src/test/java/org/acegisecurity/providers/cas/proxy/NamedCasProxyDeciderTests.java
  35. 2 0
      core/src/test/java/org/acegisecurity/providers/cas/proxy/RejectProxyTicketsTests.java
  36. 23 0
      core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java
  37. 6 0
      core/src/test/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProviderTests.java
  38. 6 0
      core/src/test/java/org/acegisecurity/providers/x509/X509AuthenticationProviderTests.java
  39. 7 0
      core/src/test/java/org/acegisecurity/providers/x509/populator/DaoX509AuthoritiesPopulatorTests.java
  40. 9 8
      core/src/test/java/org/acegisecurity/runas/RunAsImplAuthenticationProviderTests.java
  41. 13 0
      core/src/test/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilterTests.java
  42. 2 0
      core/src/test/java/org/acegisecurity/vote/AffirmativeBasedTests.java
  43. 2 0
      core/src/test/java/org/acegisecurity/vote/ConsensusBasedTests.java
  44. 10 0
      core/src/test/java/org/acegisecurity/vote/UnanimousBasedTests.java

+ 324 - 319
core/src/main/java/org/acegisecurity/acl/basic/jdbc/JdbcDaoImpl.java

@@ -29,6 +29,8 @@ import org.springframework.jdbc.core.SqlParameter;
 import org.springframework.jdbc.core.support.JdbcDaoSupport;
 import org.springframework.jdbc.object.MappingSqlQuery;
 
+import org.springframework.util.Assert;
+
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
@@ -51,9 +53,6 @@ import javax.sql.DataSource;
  * the {@link MappingSqlQuery} instance used, via the {@link
  * #initMappingSqlQueries()} extension point.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
     //~ Static fields/initializers =============================================
@@ -79,113 +78,6 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
 
     //~ Methods ================================================================
 
-    /**
-     * Returns the ACLs associated with the requested
-     * <code>AclObjectIdentity</code>.
-     * 
-     * <P>
-     * The {@link BasicAclEntry}s returned by this method will have
-     * <code>String</code>-based recipients. This will not be a problem if you
-     * are using the <code>GrantedAuthorityEffectiveAclsResolver</code>, which
-     * is the default configured against <code>BasicAclProvider</code>.
-     * </p>
-     * 
-     * <P>
-     * This method will only return ACLs for requests where the
-     * <code>AclObjectIdentity</code> is of type {@link
-     * NamedEntityObjectIdentity}. Of course, you can subclass or replace this
-     * class and support your own custom <code>AclObjectIdentity</code> types.
-     * </p>
-     *
-     * @param aclObjectIdentity for which ACL information is required (cannot
-     *        be <code>null</code> and must be an instance of
-     *        <code>NamedEntityObjectIdentity</code>)
-     *
-     * @return the ACLs that apply (without any <code>null</code>s inside the
-     *         array), or <code>null</code> if not found or if an incompatible
-     *         <code>AclObjectIdentity</code> was requested
-     */
-    public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity) {
-        String aclObjectIdentityString;
-
-        try {
-            aclObjectIdentityString = convertAclObjectIdentityToString(aclObjectIdentity);
-        } catch (IllegalArgumentException unsupported) {
-            return null; // pursuant to contract described in JavaDocs above
-        }
-
-        // Lookup the object's main properties from the RDBMS (guaranteed no nulls)
-        List objects = objectProperties.execute(aclObjectIdentityString);
-
-        if (objects.size() == 0) {
-            // this is an unknown object identity string
-            return null;
-        }
-
-        // Cast to an object properties holder (there should only be one record)
-        AclDetailsHolder propertiesInformation = (AclDetailsHolder) objects.get(0);
-
-        // Lookup the object's ACLs from RDBMS (guaranteed no nulls)
-        List acls = aclsByObjectIdentity.execute(propertiesInformation
-                .getForeignKeyId());
-
-        if (acls.size() == 0) {
-            // return merely an inheritence marker (as we know about the object but it has no related ACLs)
-            return new BasicAclEntry[] {createBasicAclEntry(propertiesInformation,
-                    null)};
-        } else {
-            // return the individual ACL instances
-            AclDetailsHolder[] aclHolders = (AclDetailsHolder[]) acls.toArray(new AclDetailsHolder[] {});
-            List toReturnAcls = new Vector();
-
-            for (int i = 0; i < aclHolders.length; i++) {
-                toReturnAcls.add(createBasicAclEntry(propertiesInformation,
-                        aclHolders[i]));
-            }
-
-            return (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {});
-        }
-    }
-
-    public void setAclsByObjectIdentity(
-        MappingSqlQuery aclsByObjectIdentityQuery) {
-        this.aclsByObjectIdentity = aclsByObjectIdentityQuery;
-    }
-
-    public MappingSqlQuery getAclsByObjectIdentity() {
-        return aclsByObjectIdentity;
-    }
-
-    /**
-     * Allows the default query string used to retrieve ACLs based on object
-     * identity to be overriden, if default table or column names need to be
-     * changed. The default query is {@link
-     * #DEF_ACLS_BY_OBJECT_IDENTITY_QUERY}; when modifying this query, ensure
-     * that all returned columns are mapped back to the same column names as
-     * in the default query.
-     *
-     * @param queryString The query string to set
-     */
-    public void setAclsByObjectIdentityQuery(String queryString) {
-        aclsByObjectIdentityQuery = queryString;
-    }
-
-    public String getAclsByObjectIdentityQuery() {
-        return aclsByObjectIdentityQuery;
-    }
-
-    public void setObjectProperties(MappingSqlQuery objectPropertiesQuery) {
-        this.objectProperties = objectPropertiesQuery;
-    }
-
-    public void setObjectPropertiesQuery(String queryString) {
-        objectPropertiesQuery = queryString;
-    }
-
-    public String getObjectPropertiesQuery() {
-        return objectPropertiesQuery;
-    }
-
     /**
      * Responsible for covering a <code>AclObjectIdentity</code> to a
      * <code>String</code> that can be located in the RDBMS.
@@ -193,17 +85,13 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
      * @param aclObjectIdentity to locate
      *
      * @return the object identity as a <code>String</code>
-     *
-     * @throws IllegalArgumentException DOCUMENT ME!
      */
     protected String convertAclObjectIdentityToString(
         AclObjectIdentity aclObjectIdentity) {
         // Ensure we can process this type of AclObjectIdentity
-        if (!(aclObjectIdentity instanceof NamedEntityObjectIdentity)) {
-            throw new IllegalArgumentException(
-                "Only aclObjectIdentity of type NamedEntityObjectIdentity supported (was passed: "
-                + aclObjectIdentity + ")");
-        }
+        Assert.isInstanceOf(NamedEntityObjectIdentity.class, aclObjectIdentity,
+            "Only aclObjectIdentity of type NamedEntityObjectIdentity supported (was passed: "
+            + aclObjectIdentity + ")");
 
         NamedEntityObjectIdentity neoi = (NamedEntityObjectIdentity) aclObjectIdentity;
 
@@ -211,19 +99,6 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
         return neoi.getClassname() + ":" + neoi.getId();
     }
 
-    protected void initDao() throws ApplicationContextException {
-        initMappingSqlQueries();
-    }
-
-    /**
-     * Extension point to allow other MappingSqlQuery objects to be substituted
-     * in a subclass
-     */
-    protected void initMappingSqlQueries() {
-        setAclsByObjectIdentity(new AclsByObjectIdentityMapping(getDataSource()));
-        setObjectProperties(new ObjectPropertiesMapping(getDataSource()));
-    }
-
     /**
      * Constructs an individual <code>BasicAclEntry</code> from the passed
      * <code>AclDetailsHolder</code>s.
@@ -263,206 +138,336 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements BasicAclDao {
 
         entry.setAclObjectIdentity(propertiesInformation.getAclObjectIdentity());
         entry.setAclObjectParentIdentity(propertiesInformation
-            .getAclObjectParentIdentity());
-
-        if (aclInformation == null) {
-            // this is an inheritence marker instance only
-            entry.setMask(0);
-            entry.setRecipient(RECIPIENT_USED_FOR_INHERITENCE_MARKER);
-        } else {
-            // this is an individual ACL entry
-            entry.setMask(aclInformation.getMask());
-            entry.setRecipient(aclInformation.getRecipient());
-        }
-
-        return entry;
-    }
-
-    //~ Inner Classes ==========================================================
-
-    /**
-     * Used to hold details of a domain object instance's properties, or an
-     * individual ACL entry.
-     * 
-     * <P>
-     * Not all properties will be set. The actual properties set will depend on
-     * which <code>MappingSqlQuery</code> creates the object.
-     * </p>
-     * 
-     * <P>
-     * Does not enforce <code>null</code>s or empty <code>String</code>s as
-     * this is performed by the <code>MappingSqlQuery</code> objects (or
-     * preferably the backend RDBMS via schema constraints).
-     * </p>
-     */
-    protected final class AclDetailsHolder {
-        private AclObjectIdentity aclObjectIdentity;
-        private AclObjectIdentity aclObjectParentIdentity;
-        private Class aclClass;
-        private Object recipient;
-        private int mask;
-        private long foreignKeyId;
+                .getAclObjectParentIdentity());
+
+            if (aclInformation == null) {
+                // this is an inheritence marker instance only
+                entry.setMask(0);
+                entry.setRecipient(RECIPIENT_USED_FOR_INHERITENCE_MARKER);
+            } else {
+                // this is an individual ACL entry
+                entry.setMask(aclInformation.getMask());
+                entry.setRecipient(aclInformation.getRecipient());
+            }
 
-        /**
-         * Record details of an individual ACL entry (usually from the
-         * ACL_PERMISSION table)
-         *
-         * @param recipient the recipient
-         * @param mask the integer to be masked
-         */
-        public AclDetailsHolder(Object recipient, int mask) {
-            this.recipient = recipient;
-            this.mask = mask;
+            return entry;
         }
 
         /**
-         * Record details of a domain object instance's properties (usually
-         * from the ACL_OBJECT_IDENTITY table)
+         * Returns the ACLs associated with the requested
+         * <code>AclObjectIdentity</code>.
+         * 
+         * <P>
+         * The {@link BasicAclEntry}s returned by this method will have
+         * <code>String</code>-based recipients. This will not be a problem if
+         * you are using the
+         * <code>GrantedAuthorityEffectiveAclsResolver</code>, which is the
+         * default configured against <code>BasicAclProvider</code>.
+         * </p>
+         * 
+         * <P>
+         * This method will only return ACLs for requests where the
+         * <code>AclObjectIdentity</code> is of type {@link
+         * NamedEntityObjectIdentity}. Of course, you can subclass or replace
+         * this class and support your own custom
+         * <code>AclObjectIdentity</code> types.
+         * </p>
+         *
+         * @param aclObjectIdentity for which ACL information is required
+         *        (cannot be <code>null</code> and must be an instance of
+         *        <code>NamedEntityObjectIdentity</code>)
          *
-         * @param foreignKeyId used by the
-         *        <code>AclsByObjectIdentityMapping</code> to locate the
-         *        individual ACL entries
-         * @param aclObjectIdentity the object identity of the domain object
-         *        instance
-         * @param aclObjectParentIdentity the object identity of the domain
-         *        object instance's parent
-         * @param aclClass the class of which a new instance which should be
-         *        created for each individual ACL entry (or an inheritence
-         *        "holder" class if there are no ACL entries)
+         * @return the ACLs that apply (without any <code>null</code>s inside
+         *         the array), or <code>null</code> if not found or if an
+         *         incompatible <code>AclObjectIdentity</code> was requested
          */
-        public AclDetailsHolder(long foreignKeyId,
-            AclObjectIdentity aclObjectIdentity,
-            AclObjectIdentity aclObjectParentIdentity, Class aclClass) {
-            this.foreignKeyId = foreignKeyId;
-            this.aclObjectIdentity = aclObjectIdentity;
-            this.aclObjectParentIdentity = aclObjectParentIdentity;
-            this.aclClass = aclClass;
-        }
-
-        public Class getAclClass() {
-            return aclClass;
-        }
-
-        public AclObjectIdentity getAclObjectIdentity() {
-            return aclObjectIdentity;
-        }
-
-        public AclObjectIdentity getAclObjectParentIdentity() {
-            return aclObjectParentIdentity;
-        }
-
-        public long getForeignKeyId() {
-            return foreignKeyId;
-        }
-
-        public int getMask() {
-            return mask;
-        }
-
-        public Object getRecipient() {
-            return recipient;
-        }
-    }
-
-    /**
-     * Query object to look up individual ACL entries.
-     * 
-     * <P>
-     * Returns the generic <code>AclDetailsHolder</code> object.
-     * </p>
-     * 
-     * <P>
-     * Guarantees to never return <code>null</code> (exceptions are thrown in
-     * the event of any issues).
-     * </p>
-     * 
-     * <P>
-     * The executed SQL requires the following information be made available
-     * from the indicated placeholders: 1. RECIPIENT, 2. MASK.
-     * </p>
-     */
-    protected class AclsByObjectIdentityMapping extends MappingSqlQuery {
-        protected AclsByObjectIdentityMapping(DataSource ds) {
-            super(ds, aclsByObjectIdentityQuery);
-            declareParameter(new SqlParameter(Types.BIGINT));
-            compile();
-        }
-
-        protected Object mapRow(ResultSet rs, int rownum)
-            throws SQLException {
-            String recipient = rs.getString(1);
-            int mask = rs.getInt(2);
-
-            if ((recipient == null) || "".equals(recipient)) {
-                throw new IllegalArgumentException("recipient required");
-            }
-
-            return new AclDetailsHolder(recipient, mask);
-        }
-    }
-
-    /**
-     * Query object to look up properties for an object identity.
-     * 
-     * <P>
-     * Returns the generic <code>AclDetailsHolder</code> object.
-     * </p>
-     * 
-     * <P>
-     * Guarantees to never return <code>null</code> (exceptions are thrown in
-     * the event of any issues).
-     * </p>
-     * 
-     * <P>
-     * The executed SQL requires the following information be made available
-     * from the indicated placeholders: 1. ID, 2. OBJECT_IDENTITY, 3.
-     * ACL_CLASS and 4. PARENT_OBJECT_IDENTITY.
-     * </p>
-     */
-    protected class ObjectPropertiesMapping extends MappingSqlQuery {
-        protected ObjectPropertiesMapping(DataSource ds) {
-            super(ds, objectPropertiesQuery);
-            declareParameter(new SqlParameter(Types.VARCHAR));
-            compile();
-        }
-
-        protected Object mapRow(ResultSet rs, int rownum)
-            throws SQLException {
-            long id = rs.getLong(1); // required
-            String objectIdentity = rs.getString(2); // required
-            String aclClass = rs.getString(3); // required
-            String parentObjectIdentity = rs.getString(4); // optional
-
-            if ((objectIdentity == null) || "".equals(objectIdentity)
-                || (aclClass == null) || "".equals(aclClass)) {
-                // shouldn't happen if DB schema defined NOT NULL columns
-                throw new IllegalArgumentException(
-                    "required DEF_OBJECT_PROPERTIES_QUERY value returned null or empty");
-            }
-
-            Class aclClazz;
+        public BasicAclEntry[] getAcls(AclObjectIdentity aclObjectIdentity) {
+            String aclObjectIdentityString;
 
             try {
-                aclClazz = this.getClass().getClassLoader().loadClass(aclClass);
-            } catch (ClassNotFoundException cnf) {
-                throw new IllegalArgumentException(cnf.getMessage());
+                aclObjectIdentityString = convertAclObjectIdentityToString(aclObjectIdentity);
+            } catch (IllegalArgumentException unsupported) {
+                return null; // pursuant to contract described in JavaDocs above
             }
 
-            return new AclDetailsHolder(id, buildIdentity(objectIdentity),
-                buildIdentity(parentObjectIdentity), aclClazz);
-        }
+            // Lookup the object's main properties from the RDBMS (guaranteed no nulls)
+            List objects = objectProperties.execute(aclObjectIdentityString);
 
-        private AclObjectIdentity buildIdentity(String identity) {
-            if (identity == null) {
-                // Must be an empty parent, so return null
+            if (objects.size() == 0) {
+                // this is an unknown object identity string
                 return null;
             }
 
-            int delim = identity.lastIndexOf(":");
-            String classname = identity.substring(0, delim);
-            String id = identity.substring(delim + 1);
-
-            return new NamedEntityObjectIdentity(classname, id);
-        }
-    }
-}
+            // Cast to an object properties holder (there should only be one record)
+            AclDetailsHolder propertiesInformation = (AclDetailsHolder) objects
+                .get(0);
+
+            // Lookup the object's ACLs from RDBMS (guaranteed no nulls)
+            List acls = aclsByObjectIdentity.execute(propertiesInformation
+                        .getForeignKeyId());
+
+                if (acls.size() == 0) {
+                    // return merely an inheritence marker (as we know about the object but it has no related ACLs)
+                    return new BasicAclEntry[] {createBasicAclEntry(propertiesInformation,
+                            null)};
+                } else {
+                    // return the individual ACL instances
+                    AclDetailsHolder[] aclHolders = (AclDetailsHolder[]) acls
+                            .toArray(new AclDetailsHolder[] {});
+                        List toReturnAcls = new Vector();
+
+                        for (int i = 0; i < aclHolders.length; i++) {
+                            toReturnAcls.add(createBasicAclEntry(
+                                    propertiesInformation, aclHolders[i]));
+                        }
+
+                        return (BasicAclEntry[]) toReturnAcls.toArray(new BasicAclEntry[] {});
+                    }
+                }
+
+                public MappingSqlQuery getAclsByObjectIdentity() {
+                    return aclsByObjectIdentity;
+                }
+
+                public String getAclsByObjectIdentityQuery() {
+                    return aclsByObjectIdentityQuery;
+                }
+
+                public String getObjectPropertiesQuery() {
+                    return objectPropertiesQuery;
+                }
+
+                protected void initDao() throws ApplicationContextException {
+                    initMappingSqlQueries();
+                }
+
+                /**
+                 * Extension point to allow other MappingSqlQuery objects to be
+                 * substituted in a subclass
+                 */
+                protected void initMappingSqlQueries() {
+                    setAclsByObjectIdentity(new AclsByObjectIdentityMapping(
+                            getDataSource()));
+                    setObjectProperties(new ObjectPropertiesMapping(
+                            getDataSource()));
+                }
+
+                public void setAclsByObjectIdentity(
+                    MappingSqlQuery aclsByObjectIdentityQuery) {
+                    this.aclsByObjectIdentity = aclsByObjectIdentityQuery;
+                }
+
+                /**
+                 * Allows the default query string used to retrieve ACLs based
+                 * on object identity to be overriden, if default table or
+                 * column names need to be changed. The default query is
+                 * {@link #DEF_ACLS_BY_OBJECT_IDENTITY_QUERY}; when modifying
+                 * this query, ensure that all returned columns are mapped
+                 * back to the same column names as in the default query.
+                 *
+                 * @param queryString The query string to set
+                 */
+                public void setAclsByObjectIdentityQuery(String queryString) {
+                    aclsByObjectIdentityQuery = queryString;
+                }
+
+                public void setObjectProperties(
+                    MappingSqlQuery objectPropertiesQuery) {
+                    this.objectProperties = objectPropertiesQuery;
+                }
+
+                public void setObjectPropertiesQuery(String queryString) {
+                    objectPropertiesQuery = queryString;
+                }
+
+                //~ Inner Classes ==========================================================
+
+                /**
+                 * Used to hold details of a domain object instance's
+                 * properties, or an individual ACL entry.
+                 * 
+                 * <P>
+                 * Not all properties will be set. The actual properties set
+                 * will depend on which <code>MappingSqlQuery</code> creates
+                 * the object.
+                 * </p>
+                 * 
+                 * <P>
+                 * Does not enforce <code>null</code>s or empty
+                 * <code>String</code>s as this is performed by the
+                 * <code>MappingSqlQuery</code> objects (or preferably the
+                 * backend RDBMS via schema constraints).
+                 * </p>
+                 */
+                protected final class AclDetailsHolder {
+                    private AclObjectIdentity aclObjectIdentity;
+                    private AclObjectIdentity aclObjectParentIdentity;
+                    private Class aclClass;
+                    private Object recipient;
+                    private int mask;
+                    private long foreignKeyId;
+
+                    /**
+                     * Record details of an individual ACL entry (usually from
+                     * the ACL_PERMISSION table)
+                     *
+                     * @param recipient the recipient
+                     * @param mask the integer to be masked
+                     */
+                    public AclDetailsHolder(Object recipient, int mask) {
+                        this.recipient = recipient;
+                        this.mask = mask;
+                    }
+
+                    /**
+                     * Record details of a domain object instance's properties
+                     * (usually from the ACL_OBJECT_IDENTITY table)
+                     *
+                     * @param foreignKeyId used by the
+                     *        <code>AclsByObjectIdentityMapping</code> to
+                     *        locate the individual ACL entries
+                     * @param aclObjectIdentity the object identity of the
+                     *        domain object instance
+                     * @param aclObjectParentIdentity the object identity of
+                     *        the domain object instance's parent
+                     * @param aclClass the class of which a new instance which
+                     *        should be created for each individual ACL entry
+                     *        (or an inheritence "holder" class if there are
+                     *        no ACL entries)
+                     */
+                    public AclDetailsHolder(long foreignKeyId,
+                        AclObjectIdentity aclObjectIdentity,
+                        AclObjectIdentity aclObjectParentIdentity,
+                        Class aclClass) {
+                        this.foreignKeyId = foreignKeyId;
+                        this.aclObjectIdentity = aclObjectIdentity;
+                        this.aclObjectParentIdentity = aclObjectParentIdentity;
+                        this.aclClass = aclClass;
+                    }
+
+                    public Class getAclClass() {
+                        return aclClass;
+                    }
+
+                    public AclObjectIdentity getAclObjectIdentity() {
+                        return aclObjectIdentity;
+                    }
+
+                    public AclObjectIdentity getAclObjectParentIdentity() {
+                        return aclObjectParentIdentity;
+                    }
+
+                    public long getForeignKeyId() {
+                        return foreignKeyId;
+                    }
+
+                    public int getMask() {
+                        return mask;
+                    }
+
+                    public Object getRecipient() {
+                        return recipient;
+                    }
+                }
+
+                /**
+                 * Query object to look up individual ACL entries.
+                 * 
+                 * <P>
+                 * Returns the generic <code>AclDetailsHolder</code> object.
+                 * </p>
+                 * 
+                 * <P>
+                 * Guarantees to never return <code>null</code> (exceptions are
+                 * thrown in the event of any issues).
+                 * </p>
+                 * 
+                 * <P>
+                 * The executed SQL requires the following information be made
+                 * available from the indicated placeholders: 1. RECIPIENT, 2.
+                 * MASK.
+                 * </p>
+                 */
+                protected class AclsByObjectIdentityMapping
+                    extends MappingSqlQuery {
+                    protected AclsByObjectIdentityMapping(DataSource ds) {
+                        super(ds, aclsByObjectIdentityQuery);
+                        declareParameter(new SqlParameter(Types.BIGINT));
+                        compile();
+                    }
+
+                    protected Object mapRow(ResultSet rs, int rownum)
+                        throws SQLException {
+                        String recipient = rs.getString(1);
+                        int mask = rs.getInt(2);
+                        Assert.hasText(recipient, "recipient required");
+
+                        return new AclDetailsHolder(recipient, mask);
+                    }
+                }
+
+                /**
+                 * Query object to look up properties for an object identity.
+                 * 
+                 * <P>
+                 * Returns the generic <code>AclDetailsHolder</code> object.
+                 * </p>
+                 * 
+                 * <P>
+                 * Guarantees to never return <code>null</code> (exceptions are
+                 * thrown in the event of any issues).
+                 * </p>
+                 * 
+                 * <P>
+                 * The executed SQL requires the following information be made
+                 * available from the indicated placeholders: 1. ID, 2.
+                 * OBJECT_IDENTITY, 3. ACL_CLASS and 4.
+                 * PARENT_OBJECT_IDENTITY.
+                 * </p>
+                 */
+                protected class ObjectPropertiesMapping extends MappingSqlQuery {
+                    protected ObjectPropertiesMapping(DataSource ds) {
+                        super(ds, objectPropertiesQuery);
+                        declareParameter(new SqlParameter(Types.VARCHAR));
+                        compile();
+                    }
+
+                    private AclObjectIdentity buildIdentity(String identity) {
+                        if (identity == null) {
+                            // Must be an empty parent, so return null
+                            return null;
+                        }
+
+                        int delim = identity.lastIndexOf(":");
+                        String classname = identity.substring(0, delim);
+                        String id = identity.substring(delim + 1);
+
+                        return new NamedEntityObjectIdentity(classname, id);
+                    }
+
+                    protected Object mapRow(ResultSet rs, int rownum)
+                        throws SQLException {
+                        long id = rs.getLong(1); // required
+                        String objectIdentity = rs.getString(2); // required
+                        String aclClass = rs.getString(3); // required
+                        String parentObjectIdentity = rs.getString(4); // optional
+                        Assert.hasText(objectIdentity,
+                            "required DEF_OBJECT_PROPERTIES_QUERY value (objectIdentity) returned null or empty");
+                        Assert.hasText(aclClass,
+                            "required DEF_OBJECT_PROPERTIES_QUERY value (aclClass) returned null or empty");
+
+                        Class aclClazz;
+
+                        try {
+                            aclClazz = this.getClass().getClassLoader()
+                                           .loadClass(aclClass);
+                        } catch (ClassNotFoundException cnf) {
+                            throw new IllegalArgumentException(cnf.getMessage());
+                        }
+
+                        return new AclDetailsHolder(id,
+                            buildIdentity(objectIdentity),
+                            buildIdentity(parentObjectIdentity), aclClazz);
+                    }
+                }
+            }

+ 27 - 16
core/src/main/java/org/acegisecurity/adapters/AuthByAdapterProvider.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -18,9 +18,15 @@ package org.acegisecurity.adapters;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.BadCredentialsException;
+
 import org.acegisecurity.providers.AuthenticationProvider;
 
 import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 
@@ -38,28 +44,20 @@ import org.springframework.util.Assert;
  * <P>
  * If the key does not match, a <code>BadCredentialsException</code> is thrown.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class AuthByAdapterProvider implements InitializingBean,
-    AuthenticationProvider {
+    AuthenticationProvider, MessageSourceAware {
     //~ Instance fields ========================================================
 
+    protected MessageSourceAccessor messages;
     private String key;
 
     //~ Methods ================================================================
 
-    public void setKey(String key) {
-        this.key = key;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
     public void afterPropertiesSet() throws Exception {
-        Assert.notNull(key, "A Key is required and should match that configured for the adapters");
+        Assert.notNull(key,
+            "A Key is required and should match that configured for the adapters");
+        Assert.notNull(messages, "A message source must be set");
     }
 
     public Authentication authenticate(Authentication authentication)
@@ -69,11 +67,24 @@ public class AuthByAdapterProvider implements InitializingBean,
         if (token.getKeyHash() == key.hashCode()) {
             return authentication;
         } else {
-            throw new BadCredentialsException(
-                "The presented AuthByAdapter implementation does not contain the expected key");
+            throw new BadCredentialsException(messages.getMessage(
+                    "AuthByAdapterProvider.incorrectKey",
+                    "The presented AuthByAdapter implementation does not contain the expected key"));
         }
     }
 
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
     public boolean supports(Class authentication) {
         if (AuthByAdapter.class.isAssignableFrom(authentication)) {
             return true;

+ 57 - 48
core/src/main/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProvider.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -19,6 +19,7 @@ import org.acegisecurity.AccessDeniedException;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.ConfigAttribute;
 import org.acegisecurity.ConfigAttributeDefinition;
+
 import org.acegisecurity.acl.AclEntry;
 import org.acegisecurity.acl.AclManager;
 import org.acegisecurity.acl.basic.BasicAclEntry;
@@ -28,6 +29,11 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 import java.util.Iterator;
@@ -45,9 +51,8 @@ import java.util.Iterator;
  * (ACL) permissions associated with a domain object instance for the current
  * <code>Authentication</code> object. This class is designed to process
  * {@link AclEntry}s that are subclasses of {@link
- * org.acegisecurity.acl.basic.BasicAclEntry} only. Generally these
- * are obtained by using the {@link
- * org.acegisecurity.acl.basic.BasicAclProvider}.
+ * org.acegisecurity.acl.basic.BasicAclEntry} only. Generally these are
+ * obtained by using the {@link org.acegisecurity.acl.basic.BasicAclProvider}.
  * </p>
  * 
  * <p>
@@ -55,8 +60,8 @@ import java.util.Iterator;
  * ConfigAttribute#getAttribute()} matches the {@link
  * #processConfigAttribute}. The provider will then lookup the ACLs from the
  * <code>AclManager</code> and ensure the principal is {@link
- * org.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for
- * at least one of the {@link #requirePermission}s.
+ * org.acegisecurity.acl.basic.BasicAclEntry#isPermitted(int)} for at least
+ * one of the {@link #requirePermission}s.
  * </p>
  * 
  * <p>
@@ -74,9 +79,8 @@ import java.util.Iterator;
  * <p>
  * The <code>AclManager</code> is allowed to return any implementations of
  * <code>AclEntry</code> it wishes. However, this provider will only be able
- * to validate against <code>BasicAclEntry</code>s, and thus access
- * will be denied if no <code>AclEntry</code> is of type
- * <code>BasicAclEntry</code>.
+ * to validate against <code>BasicAclEntry</code>s, and thus access will be
+ * denied if no <code>AclEntry</code> is of type <code>BasicAclEntry</code>.
  * </p>
  * 
  * <p>
@@ -87,12 +91,9 @@ import java.util.Iterator;
  * <p>
  * All comparisons and prefixes are case sensitive.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class BasicAclEntryAfterInvocationProvider
-    implements AfterInvocationProvider, InitializingBean {
+    implements AfterInvocationProvider, InitializingBean, MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     protected static final Log logger = LogFactory.getLog(BasicAclEntryAfterInvocationProvider.class);
@@ -100,41 +101,21 @@ public class BasicAclEntryAfterInvocationProvider
     //~ Instance fields ========================================================
 
     private AclManager aclManager;
+    protected MessageSourceAccessor messages;
     private String processConfigAttribute = "AFTER_ACL_READ";
     private int[] requirePermission = {SimpleAclEntry.READ};
 
     //~ Methods ================================================================
 
-    public void setAclManager(AclManager aclManager) {
-        this.aclManager = aclManager;
-    }
-
-    public AclManager getAclManager() {
-        return aclManager;
-    }
-
-    public void setProcessConfigAttribute(String processConfigAttribute) {
-        this.processConfigAttribute = processConfigAttribute;
-    }
-
-    public String getProcessConfigAttribute() {
-        return processConfigAttribute;
-    }
-
-    public void setRequirePermission(int[] requirePermission) {
-        this.requirePermission = requirePermission;
-    }
-
-    public int[] getRequirePermission() {
-        return requirePermission;
-    }
-
     public void afterPropertiesSet() throws Exception {
-        Assert.notNull(processConfigAttribute, "A processConfigAttribute is mandatory");
+        Assert.notNull(processConfigAttribute,
+            "A processConfigAttribute is mandatory");
         Assert.notNull(aclManager, "An aclManager is mandatory");
+        Assert.notNull(messages, "A message source must be set");
 
         if ((requirePermission == null) || (requirePermission.length == 0)) {
-            throw new IllegalArgumentException("One or more requirePermission entries is mandatory");
+            throw new IllegalArgumentException(
+                "One or more requirePermission entries is mandatory");
         }
     }
 
@@ -162,16 +143,16 @@ public class BasicAclEntryAfterInvocationProvider
                         authentication);
 
                 if ((acls == null) || (acls.length == 0)) {
-                    throw new AccessDeniedException("Authentication: "
-                        + authentication.toString()
-                        + " has NO permissions at all to the domain object: "
-                        + returnedObject);
+                    throw new AccessDeniedException(messages.getMessage(
+                            "BasicAclEntryAfterInvocationProvider.noPermission",
+                            new Object[] {authentication.getName(), returnedObject},
+                            "Authentication {0} has NO permissions at all to the domain object {1}"));
                 }
 
                 for (int i = 0; i < acls.length; i++) {
                     // Locate processable AclEntrys
                     if (acls[i] instanceof BasicAclEntry) {
-                    	BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
+                        BasicAclEntry processableAcl = (BasicAclEntry) acls[i];
 
                         // See if principal has any of the required permissions
                         for (int y = 0; y < requirePermission.length; y++) {
@@ -190,16 +171,44 @@ public class BasicAclEntryAfterInvocationProvider
                 }
 
                 // No permissions match
-                throw new AccessDeniedException("Authentication: "
-                    + authentication.toString()
-                    + " has ACL permissions to the domain object, but not the required ACL permission to the domain object: "
-                    + returnedObject);
+                throw new AccessDeniedException(messages.getMessage(
+                        "BasicAclEntryAfterInvocationProvider.insufficientPermission",
+                        new Object[] {authentication.getName(), returnedObject},
+                        "Authentication {0} has ACL permissions to the domain object, but not the required ACL permission to the domain object {1}"));
             }
         }
 
         return returnedObject;
     }
 
+    public AclManager getAclManager() {
+        return aclManager;
+    }
+
+    public String getProcessConfigAttribute() {
+        return processConfigAttribute;
+    }
+
+    public int[] getRequirePermission() {
+        return requirePermission;
+    }
+
+    public void setAclManager(AclManager aclManager) {
+        this.aclManager = aclManager;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
+    public void setProcessConfigAttribute(String processConfigAttribute) {
+        this.processConfigAttribute = processConfigAttribute;
+    }
+
+    public void setRequirePermission(int[] requirePermission) {
+        this.requirePermission = requirePermission;
+    }
+
     public boolean supports(ConfigAttribute attribute) {
         if ((attribute.getAttribute() != null)
             && attribute.getAttribute().equals(getProcessConfigAttribute())) {

+ 116 - 90
core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImpl.java

@@ -19,6 +19,11 @@ import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 
 import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 
@@ -30,124 +35,145 @@ import org.springframework.util.Assert;
  * By default uses {@link org.acegisecurity.concurrent.SessionRegistryImpl},
  * although any <code>SessionRegistry</code> may be used.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class ConcurrentSessionControllerImpl
-    implements ConcurrentSessionController, InitializingBean {
+    implements ConcurrentSessionController, InitializingBean,
+        MessageSourceAware {
     //~ Instance fields ========================================================
 
+    protected MessageSourceAccessor messages;
     private SessionRegistry sessionRegistry = new SessionRegistryImpl();
-    private int maximumSessions = 1;
     private boolean exceptionIfMaximumExceeded = false;
+    private int maximumSessions = 1;
 
     //~ Methods ================================================================
 
-    public void setMaximumSessions(int maximumSessions) {
-        this.maximumSessions = maximumSessions;
-    }
-
-    public void setSessionRegistry(SessionRegistry sessionRegistry) {
-        this.sessionRegistry = sessionRegistry;
-    }
-
-    public void setExceptionIfMaximumExceeded(boolean exceptionIfMaximumExceeded) {
-		this.exceptionIfMaximumExceeded = exceptionIfMaximumExceeded;
-	}
-
-	public void afterPropertiesSet() throws Exception {
+    public void afterPropertiesSet() throws Exception {
         Assert.notNull(sessionRegistry, "SessionRegistry required");
         Assert.isTrue(maximumSessions != 0,
             "MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum");
+        Assert.notNull(this.messages, "A message source must be set");
     }
 
-    public void checkAuthenticationAllowed(Authentication request)
-        throws AuthenticationException {
-        Assert.notNull(request,
-            "Authentication request cannot be null (violation of interface contract)");
-
-        Object principal = SessionRegistryUtils
-            .obtainPrincipalFromAuthentication(request);
-        String sessionId = SessionRegistryUtils
-            .obtainSessionIdFromAuthentication(request);
-
-        SessionInformation[] sessions = sessionRegistry.getAllSessions(principal);
-
-        int sessionCount = 0;
-
-        if (sessions != null) {
-            sessionCount = sessions.length;
-        }
-
-        int allowableSessions = getMaximumSessionsForThisUser(request);
-        Assert.isTrue(allowableSessions != 0,
-            "getMaximumSessionsForThisUser() must return either -1 to allow unlimited logins, or a positive integer to specify a maximum");
-
-        if (sessionCount < allowableSessions) {
-            return;
-        } else if (sessionCount == allowableSessions) {
-            // Only permit it though if this request is associated with one of the sessions
-            for (int i = 0; i < sessionCount; i++) {
-                if (sessions[i].getSessionId().equals(sessionId)) {
-                    return;
-                }
-            }
-        }
-
-        allowableSessionsExceeded(sessionId, sessions, allowableSessions, sessionRegistry);
-    }
-    
     /**
      * Allows subclasses to customise behaviour when too many sessions are
      * detected.
-     * 
+     *
      * @param sessionId the session ID of the present request
-     * @param sessions either <code>null</code> or all unexpired sessions associated with the principal
-     * @param registry an instance of the <code>SessionRegistry</code> for subclass use
+     * @param sessions either <code>null</code> or all unexpired sessions
+     *        associated with the principal
+     * @param allowableSessions DOCUMENT ME!
+     * @param registry an instance of the <code>SessionRegistry</code> for
+     *        subclass use
+     *
+     * @throws ConcurrentLoginException DOCUMENT ME!
      */
-    protected void allowableSessionsExceeded(String sessionId, SessionInformation[] sessions, int allowableSessions, SessionRegistry registry) {
-        if (exceptionIfMaximumExceeded || sessions == null) {
-        	throw new ConcurrentLoginException("Maximum sessions of "
-                    + allowableSessions + " for this principal exceeded");
+    protected void allowableSessionsExceeded(String sessionId,
+        SessionInformation[] sessions, int allowableSessions,
+        SessionRegistry registry) {
+        if (exceptionIfMaximumExceeded || (sessions == null)) {
+            throw new ConcurrentLoginException(messages.getMessage(
+                    "ConcurrentSessionControllerImpl.exceededAllowed",
+                    new Object[] {new Integer(allowableSessions)},
+                    "Maximum sessions of {0} for this principal exceeded"));
         }
-        
+
         // Determine least recently used session, and mark it for invalidation
         SessionInformation leastRecentlyUsed = null;
+
         for (int i = 0; i < sessions.length; i++) {
-        	if (leastRecentlyUsed == null || sessions[i].getLastRequest().before(leastRecentlyUsed.getLastRequest())) {
-        		leastRecentlyUsed = sessions[i];
-        	}
+            if ((leastRecentlyUsed == null)
+                || sessions[i].getLastRequest()
+                              .before(leastRecentlyUsed.getLastRequest())) {
+                leastRecentlyUsed = sessions[i];
+            }
         }
-        
+
         leastRecentlyUsed.expireNow();
     }
 
-    public void registerSuccessfulAuthentication(Authentication authentication) {
-        Assert.notNull(authentication,
-            "Authentication cannot be null (violation of interface contract)");
+    public void checkAuthenticationAllowed(Authentication request)
+        throws AuthenticationException {
+        Assert.notNull(request,
+            "Authentication request cannot be null (violation of interface contract)");
 
         Object principal = SessionRegistryUtils
-            .obtainPrincipalFromAuthentication(authentication);
-        String sessionId = SessionRegistryUtils
-            .obtainSessionIdFromAuthentication(authentication);
+                .obtainPrincipalFromAuthentication(request);
+            String sessionId = SessionRegistryUtils
+                    .obtainSessionIdFromAuthentication(request);
 
-        sessionRegistry.removeSessionInformation(sessionId);
-        sessionRegistry.registerNewSession(sessionId, principal);
-    }
+                SessionInformation[] sessions = sessionRegistry.getAllSessions(principal);
 
-    /**
-     * Method intended for use by subclasses to override the maximum number of
-     * sessions that are permitted for a particular authentication. The
-     * default implementation simply returns the <code>maximumSessions</code>
-     * value for the bean.
-     *
-     * @param authentication to determine the maximum sessions for
-     *
-     * @return either -1 meaning unlimited, or a positive integer to limit
-     *         (never zero)
-     */
-    protected int getMaximumSessionsForThisUser(Authentication authentication) {
-        return maximumSessions;
-    }
-}
+                int sessionCount = 0;
+
+                if (sessions != null) {
+                    sessionCount = sessions.length;
+                }
+
+                int allowableSessions = getMaximumSessionsForThisUser(request);
+                Assert.isTrue(allowableSessions != 0,
+                    "getMaximumSessionsForThisUser() must return either -1 to allow unlimited logins, or a positive integer to specify a maximum");
+
+                if (sessionCount < allowableSessions) {
+                    return;
+                } else if (sessionCount == allowableSessions) {
+                    // Only permit it though if this request is associated with one of the sessions
+                    for (int i = 0; i < sessionCount; i++) {
+                        if (sessions[i].getSessionId().equals(sessionId)) {
+                            return;
+                        }
+                    }
+                }
+
+                allowableSessionsExceeded(sessionId, sessions,
+                    allowableSessions, sessionRegistry);
+            }
+
+            /**
+             * Method intended for use by subclasses to override the maximum
+             * number of sessions that are permitted for a particular
+             * authentication. The default implementation simply returns the
+             * <code>maximumSessions</code> value for the bean.
+             *
+             * @param authentication to determine the maximum sessions for
+             *
+             * @return either -1 meaning unlimited, or a positive integer to
+             *         limit (never zero)
+             */
+            protected int getMaximumSessionsForThisUser(
+                Authentication authentication) {
+                return maximumSessions;
+            }
+
+            public void registerSuccessfulAuthentication(
+                Authentication authentication) {
+                Assert.notNull(authentication,
+                    "Authentication cannot be null (violation of interface contract)");
+
+                Object principal = SessionRegistryUtils
+                        .obtainPrincipalFromAuthentication(authentication);
+                    String sessionId = SessionRegistryUtils
+                            .obtainSessionIdFromAuthentication(authentication);
+
+                        sessionRegistry.removeSessionInformation(sessionId);
+                        sessionRegistry.registerNewSession(sessionId, principal);
+                    }
+
+                    public void setExceptionIfMaximumExceeded(
+                        boolean exceptionIfMaximumExceeded) {
+                        this.exceptionIfMaximumExceeded = exceptionIfMaximumExceeded;
+                    }
+
+                    public void setMaximumSessions(int maximumSessions) {
+                        this.maximumSessions = maximumSessions;
+                    }
+
+                    public void setMessageSource(MessageSource messageSource) {
+                        this.messages = new MessageSourceAccessor(messageSource);
+                    }
+
+                    public void setSessionRegistry(
+                        SessionRegistry sessionRegistry) {
+                        this.sessionRegistry = sessionRegistry;
+                    }
+                }

+ 311 - 296
core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java

@@ -42,6 +42,9 @@ import org.springframework.beans.factory.InitializingBean;
 
 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.util.Assert;
 
@@ -135,12 +138,9 @@ import java.util.Set;
  * </li>
  * </ol>
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public abstract class AbstractSecurityInterceptor implements InitializingBean,
-    ApplicationEventPublisherAware {
+    ApplicationEventPublisherAware, MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     protected static final Log logger = LogFactory.getLog(AbstractSecurityInterceptor.class);
@@ -151,6 +151,7 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
     private AfterInvocationManager afterInvocationManager;
     private ApplicationEventPublisher eventPublisher;
     private AuthenticationManager authenticationManager;
+    protected MessageSourceAccessor messages;
     private RunAsManager runAsManager = new NullRunAsManager();
     private boolean alwaysReauthenticate = false;
     private boolean rejectPublicInvocations = false;
@@ -158,194 +159,6 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
 
     //~ Methods ================================================================
 
-    public void setAfterInvocationManager(
-        AfterInvocationManager afterInvocationManager) {
-        this.afterInvocationManager = afterInvocationManager;
-    }
-
-    public AfterInvocationManager getAfterInvocationManager() {
-        return afterInvocationManager;
-    }
-
-    /**
-     * Indicates whether the <code>AbstractSecurityInterceptor</code> should
-     * ignore the {@link Authentication#isAuthenticated()} property. Defaults
-     * to <code>false</code>, meaning by default the
-     * <code>Authentication.isAuthenticated()</code> property is trusted and
-     * re-authentication will not occur if the principal has already been
-     * authenticated.
-     *
-     * @param alwaysReauthenticate <code>true</code> to force
-     *        <code>AbstractSecurityInterceptor</code> to disregard the value
-     *        of <code>Authentication.isAuthenticated()</code> and always
-     *        re-authenticate the request (defaults to <code>false</code>).
-     */
-    public void setAlwaysReauthenticate(boolean alwaysReauthenticate) {
-        this.alwaysReauthenticate = alwaysReauthenticate;
-    }
-
-    public boolean isAlwaysReauthenticate() {
-        return alwaysReauthenticate;
-    }
-
-    public void setApplicationEventPublisher(
-        ApplicationEventPublisher eventPublisher) {
-        this.eventPublisher = eventPublisher;
-    }
-
-    /**
-     * Indicates the type of secure objects the subclass will be presenting to
-     * the abstract parent for processing. This is used to ensure
-     * collaborators wired to the <code>AbstractSecurityInterceptor</code> all
-     * support the indicated secure object class.
-     *
-     * @return the type of secure object the subclass provides services for
-     */
-    public abstract Class getSecureObjectClass();
-
-    public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
-
-    public void setAccessDecisionManager(
-        AccessDecisionManager accessDecisionManager) {
-        this.accessDecisionManager = accessDecisionManager;
-    }
-
-    public AccessDecisionManager getAccessDecisionManager() {
-        return accessDecisionManager;
-    }
-
-    public void setAuthenticationManager(AuthenticationManager newManager) {
-        this.authenticationManager = newManager;
-    }
-
-    public AuthenticationManager getAuthenticationManager() {
-        return this.authenticationManager;
-    }
-
-    /**
-     * By rejecting public invocations (and setting this property to
-     * <code>true</code>), essentially you are ensuring that every secure
-     * object invocation advised by <code>AbstractSecurityInterceptor</code>
-     * has a configuration attribute defined. This is useful to ensure a "fail
-     * safe" mode where undeclared secure objects will be rejected and
-     * configuration omissions detected early. An
-     * <code>IllegalArgumentException</code> will be thrown by the
-     * <code>AbstractSecurityInterceptor</code> if you set this property to
-     * <code>true</code> and an attempt is made to invoke a secure object that
-     * has no configuration attributes.
-     *
-     * @param rejectPublicInvocations set to <code>true</code> to reject
-     *        invocations of secure objects that have no configuration
-     *        attributes (by default it is <code>true</code> which treats
-     *        undeclared secure objects as "public" or unauthorized)
-     */
-    public void setRejectPublicInvocations(boolean rejectPublicInvocations) {
-        this.rejectPublicInvocations = rejectPublicInvocations;
-    }
-
-    public boolean isRejectPublicInvocations() {
-        return rejectPublicInvocations;
-    }
-
-    public void setRunAsManager(RunAsManager runAsManager) {
-        this.runAsManager = runAsManager;
-    }
-
-    public RunAsManager getRunAsManager() {
-        return runAsManager;
-    }
-
-    public void setValidateConfigAttributes(boolean validateConfigAttributes) {
-        this.validateConfigAttributes = validateConfigAttributes;
-    }
-
-    public boolean isValidateConfigAttributes() {
-        return validateConfigAttributes;
-    }
-
-    public void afterPropertiesSet() throws Exception {
-        Assert.notNull(getSecureObjectClass(),
-            "Subclass must provide a non-null response to getSecureObjectClass()");
-
-        Assert.notNull(this.authenticationManager,
-            "An AuthenticationManager is required");
-
-        Assert.notNull(this.accessDecisionManager,
-            "An AccessDecisionManager is required");
-
-        Assert.notNull(this.runAsManager, "A RunAsManager is required");
-
-        Assert.notNull(this.obtainObjectDefinitionSource(),
-            "An ObjectDefinitionSource is required");
-
-        if (!this.obtainObjectDefinitionSource().supports(getSecureObjectClass())) {
-            throw new IllegalArgumentException(
-                "ObjectDefinitionSource does not support secure object class: "
-                + getSecureObjectClass());
-        }
-
-        if (!this.runAsManager.supports(getSecureObjectClass())) {
-            throw new IllegalArgumentException(
-                "RunAsManager does not support secure object class: "
-                + getSecureObjectClass());
-        }
-
-        if (!this.accessDecisionManager.supports(getSecureObjectClass())) {
-            throw new IllegalArgumentException(
-                "AccessDecisionManager does not support secure object class: "
-                + getSecureObjectClass());
-        }
-
-        if ((this.afterInvocationManager != null)
-            && !this.afterInvocationManager.supports(getSecureObjectClass())) {
-            throw new IllegalArgumentException(
-                "AfterInvocationManager does not support secure object class: "
-                + getSecureObjectClass());
-        }
-
-        if (this.validateConfigAttributes) {
-            Iterator iter = this.obtainObjectDefinitionSource()
-                                .getConfigAttributeDefinitions();
-
-            if (iter == null) {
-                if (logger.isWarnEnabled()) {
-                    logger.warn(
-                        "Could not validate configuration attributes as the MethodDefinitionSource did not return a ConfigAttributeDefinition Iterator");
-                }
-            } else {
-                Set set = new HashSet();
-
-                while (iter.hasNext()) {
-                    ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter
-                        .next();
-                    Iterator attributes = def.getConfigAttributes();
-
-                    while (attributes.hasNext()) {
-                        ConfigAttribute attr = (ConfigAttribute) attributes
-                            .next();
-
-                        if (!this.runAsManager.supports(attr)
-                            && !this.accessDecisionManager.supports(attr)
-                            && ((this.afterInvocationManager == null)
-                            || !this.afterInvocationManager.supports(attr))) {
-                            set.add(attr);
-                        }
-                    }
-                }
-
-                if (set.size() == 0) {
-                    if (logger.isInfoEnabled()) {
-                        logger.info("Validated configuration attributes");
-                    }
-                } else {
-                    throw new IllegalArgumentException(
-                        "Unsupported configuration attributes: "
-                        + set.toString());
-                }
-            }
-        }
-    }
-
     /**
      * Completes the work of the <code>AbstractSecurityInterceptor</code> after
      * the secure object invocation has been complete
@@ -371,151 +184,353 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean,
                     + token.getAuthentication().toString());
             }
 
-            SecurityContextHolder.getContext().setAuthentication(token
-                .getAuthentication());
+            SecurityContextHolder.getContext()
+                                 .setAuthentication(token.getAuthentication());
         }
 
         if (afterInvocationManager != null) {
             returnedObject = afterInvocationManager.decide(token
-                    .getAuthentication(), token.getSecureObject(),
-                    token.getAttr(), returnedObject);
+                        .getAuthentication(), token.getSecureObject(),
+                        token.getAttr(), returnedObject);
+            }
+
+            return returnedObject;
         }
 
-        return returnedObject;
-    }
+        public void afterPropertiesSet() throws Exception {
+            Assert.notNull(getSecureObjectClass(),
+                "Subclass must provide a non-null response to getSecureObjectClass()");
 
-    protected InterceptorStatusToken beforeInvocation(Object object) {
-        Assert.notNull(object, "Object was null");
-        Assert.isTrue(getSecureObjectClass().isAssignableFrom(object.getClass()),
-            "Security invocation attempted for object "
-            + object.getClass().getName()
-            + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
-            + getSecureObjectClass());
+            Assert.notNull(this.messages, "A message source must be set");
+            Assert.notNull(this.authenticationManager,
+                "An AuthenticationManager is required");
 
-        ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource()
-                                             .getAttributes(object);
+            Assert.notNull(this.accessDecisionManager,
+                "An AccessDecisionManager is required");
 
-        if ((attr == null) && rejectPublicInvocations) {
-            throw new IllegalArgumentException(
-                "No public invocations are allowed via this AbstractSecurityInterceptor. This indicates a configuration error because the AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'");
-        }
+            Assert.notNull(this.runAsManager, "A RunAsManager is required");
 
-        if (attr != null) {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Secure object: " + object.toString()
-                    + "; ConfigAttributes: " + attr.toString());
+            Assert.notNull(this.obtainObjectDefinitionSource(),
+                "An ObjectDefinitionSource is required");
+
+            if (!this.obtainObjectDefinitionSource()
+                     .supports(getSecureObjectClass())) {
+                throw new IllegalArgumentException(
+                    "ObjectDefinitionSource does not support secure object class: "
+                    + getSecureObjectClass());
             }
 
-            // We check for just the property we're interested in (we do
-            // not call Context.validate() like the ContextInterceptor)
-            if (SecurityContextHolder.getContext().getAuthentication() == null) {
-                credentialsNotFound("Authentication credentials were not found in the SecurityContext",
-                    object, attr);
+            if (!this.runAsManager.supports(getSecureObjectClass())) {
+                throw new IllegalArgumentException(
+                    "RunAsManager does not support secure object class: "
+                    + getSecureObjectClass());
             }
 
-            // Attempt authentication if not already authenticated, or user always wants reauthentication
-            Authentication authenticated;
+            if (!this.accessDecisionManager.supports(getSecureObjectClass())) {
+                throw new IllegalArgumentException(
+                    "AccessDecisionManager does not support secure object class: "
+                    + getSecureObjectClass());
+            }
 
-            if (!SecurityContextHolder.getContext().getAuthentication()
-                                      .isAuthenticated()
-                || alwaysReauthenticate) {
-                try {
-                    authenticated = this.authenticationManager.authenticate(SecurityContextHolder.getContext()
-                                                                                                 .getAuthentication());
-                } catch (AuthenticationException authenticationException) {
-                    throw authenticationException;
+            if ((this.afterInvocationManager != null)
+                && !this.afterInvocationManager.supports(getSecureObjectClass())) {
+                throw new IllegalArgumentException(
+                    "AfterInvocationManager does not support secure object class: "
+                    + getSecureObjectClass());
+            }
+
+            if (this.validateConfigAttributes) {
+                Iterator iter = this.obtainObjectDefinitionSource()
+                                    .getConfigAttributeDefinitions();
+
+                if (iter == null) {
+                    if (logger.isWarnEnabled()) {
+                        logger.warn(
+                            "Could not validate configuration attributes as the MethodDefinitionSource did not return a ConfigAttributeDefinition Iterator");
+                    }
+                } else {
+                    Set set = new HashSet();
+
+                    while (iter.hasNext()) {
+                        ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter
+                            .next();
+                        Iterator attributes = def.getConfigAttributes();
+
+                        while (attributes.hasNext()) {
+                            ConfigAttribute attr = (ConfigAttribute) attributes
+                                .next();
+
+                            if (!this.runAsManager.supports(attr)
+                                && !this.accessDecisionManager.supports(attr)
+                                && ((this.afterInvocationManager == null)
+                                || !this.afterInvocationManager.supports(attr))) {
+                                set.add(attr);
+                            }
+                        }
+                    }
+
+                    if (set.size() == 0) {
+                        if (logger.isInfoEnabled()) {
+                            logger.info("Validated configuration attributes");
+                        }
+                    } else {
+                        throw new IllegalArgumentException(
+                            "Unsupported configuration attributes: "
+                            + set.toString());
+                    }
                 }
+            }
+        }
+
+        protected InterceptorStatusToken beforeInvocation(Object object) {
+            Assert.notNull(object, "Object was null");
+            Assert.isTrue(getSecureObjectClass()
+                              .isAssignableFrom(object.getClass()),
+                "Security invocation attempted for object "
+                + object.getClass().getName()
+                + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+                + getSecureObjectClass());
+
+            ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource()
+                                                 .getAttributes(object);
+
+            if ((attr == null) && rejectPublicInvocations) {
+                throw new IllegalArgumentException(
+                    "No public invocations are allowed via this AbstractSecurityInterceptor. This indicates a configuration error because the AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'");
+            }
 
-                // We don't authenticated.setAuthentication(true), because each provider should do that
+            if (attr != null) {
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Successfully Authenticated: "
-                        + authenticated.toString());
+                    logger.debug("Secure object: " + object.toString()
+                        + "; ConfigAttributes: " + attr.toString());
                 }
 
-                SecurityContextHolder.getContext().setAuthentication(authenticated);
-            } else {
-                authenticated = SecurityContextHolder.getContext()
-                                                     .getAuthentication();
+                // We check for just the property we're interested in (we do
+                // not call Context.validate() like the ContextInterceptor)
+                if (SecurityContextHolder.getContext().getAuthentication() == null) {
+                    credentialsNotFound(messages.getMessage(
+                            "AbstractSecurityInterceptor.authenticationNotFound",
+                            "An Authentication object was not found in the SecurityContext"),
+                        object, attr);
+                }
+
+                // Attempt authentication if not already authenticated, or user always wants reauthentication
+                Authentication authenticated;
+
+                if (!SecurityContextHolder.getContext().getAuthentication()
+                                          .isAuthenticated()
+                    || alwaysReauthenticate) {
+                    try {
+                        authenticated = this.authenticationManager.authenticate(SecurityContextHolder.getContext()
+                                                                                                     .getAuthentication());
+                    } catch (AuthenticationException authenticationException) {
+                        throw authenticationException;
+                    }
+
+                    // We don't authenticated.setAuthentication(true), because each provider should do that
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Successfully Authenticated: "
+                            + authenticated.toString());
+                    }
+
+                    SecurityContextHolder.getContext()
+                                         .setAuthentication(authenticated);
+                } else {
+                    authenticated = SecurityContextHolder.getContext()
+                                                         .getAuthentication();
+
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Previously Authenticated: "
+                            + authenticated.toString());
+                    }
+                }
+
+                // Attempt authorization
+                try {
+                    this.accessDecisionManager.decide(authenticated, object,
+                        attr);
+                } catch (AccessDeniedException accessDeniedException) {
+                    AuthorizationFailureEvent event = new AuthorizationFailureEvent(object,
+                            attr, authenticated, accessDeniedException);
+                    this.eventPublisher.publishEvent(event);
+
+                    throw accessDeniedException;
+                }
 
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Previously Authenticated: "
-                        + authenticated.toString());
+                    logger.debug("Authorization successful");
                 }
-            }
 
-            // Attempt authorization
-            try {
-                this.accessDecisionManager.decide(authenticated, object, attr);
-            } catch (AccessDeniedException accessDeniedException) {
-                AuthorizationFailureEvent event = new AuthorizationFailureEvent(object,
-                        attr, authenticated, accessDeniedException);
+                AuthorizedEvent event = new AuthorizedEvent(object, attr,
+                        authenticated);
                 this.eventPublisher.publishEvent(event);
 
-                throw accessDeniedException;
-            }
+                // Attempt to run as a different user
+                Authentication runAs = this.runAsManager.buildRunAs(authenticated,
+                        object, attr);
 
-            if (logger.isDebugEnabled()) {
-                logger.debug("Authorization successful");
-            }
+                if (runAs == null) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug(
+                            "RunAsManager did not change Authentication object");
+                    }
 
-            AuthorizedEvent event = new AuthorizedEvent(object, attr,
-                    authenticated);
-            this.eventPublisher.publishEvent(event);
+                    return new InterceptorStatusToken(authenticated, false,
+                        attr, object); // no further work post-invocation
+                } else {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Switching to RunAs Authentication: "
+                            + runAs.toString());
+                    }
 
-            // Attempt to run as a different user
-            Authentication runAs = this.runAsManager.buildRunAs(authenticated,
-                    object, attr);
+                    SecurityContextHolder.getContext().setAuthentication(runAs);
 
-            if (runAs == null) {
-                if (logger.isDebugEnabled()) {
-                    logger.debug(
-                        "RunAsManager did not change Authentication object");
+                    return new InterceptorStatusToken(authenticated, true,
+                        attr, object); // revert to token.Authenticated post-invocation
                 }
-
-                return new InterceptorStatusToken(authenticated, false, attr,
-                    object); // no further work post-invocation
             } else {
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Switching to RunAs Authentication: "
-                        + runAs.toString());
+                    logger.debug("Public object - authentication not attempted");
                 }
 
-                SecurityContextHolder.getContext().setAuthentication(runAs);
+                this.eventPublisher.publishEvent(new PublicInvocationEvent(
+                        object));
 
-                return new InterceptorStatusToken(authenticated, true, attr,
-                    object); // revert to token.Authenticated post-invocation
-            }
-        } else {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Public object - authentication not attempted");
+                return null; // no further work post-invocation
             }
+        }
 
-            this.eventPublisher.publishEvent(new PublicInvocationEvent(object));
+        /**
+         * Helper method which generates an exception containing the passed
+         * reason, and publishes an event to the application context.
+         * 
+         * <p>
+         * Always throws an exception.
+         * </p>
+         *
+         * @param reason to be provided in the exception detail
+         * @param secureObject that was being called
+         * @param configAttribs that were defined for the secureObject
+         */
+        private void credentialsNotFound(String reason, Object secureObject,
+            ConfigAttributeDefinition configAttribs) {
+            AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason);
+
+            AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject,
+                    configAttribs, exception);
+            this.eventPublisher.publishEvent(event);
 
-            return null; // no further work post-invocation
+            throw exception;
         }
-    }
 
-    /**
-     * Helper method which generates an exception containing the passed reason,
-     * and publishes an event to the application context.
-     * 
-     * <p>
-     * Always throws an exception.
-     * </p>
-     *
-     * @param reason to be provided in the exception detail
-     * @param secureObject that was being called
-     * @param configAttribs that were defined for the secureObject
-     */
-    private void credentialsNotFound(String reason, Object secureObject,
-        ConfigAttributeDefinition configAttribs) {
-        AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason);
+        public AccessDecisionManager getAccessDecisionManager() {
+            return accessDecisionManager;
+        }
+
+        public AfterInvocationManager getAfterInvocationManager() {
+            return afterInvocationManager;
+        }
+
+        public AuthenticationManager getAuthenticationManager() {
+            return this.authenticationManager;
+        }
 
-        AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject,
-                configAttribs, exception);
-        this.eventPublisher.publishEvent(event);
+        public RunAsManager getRunAsManager() {
+            return runAsManager;
+        }
+
+        /**
+         * Indicates the type of secure objects the subclass will be presenting
+         * to the abstract parent for processing. This is used to ensure
+         * collaborators wired to the <code>AbstractSecurityInterceptor</code>
+         * all support the indicated secure object class.
+         *
+         * @return the type of secure object the subclass provides services for
+         */
+        public abstract Class getSecureObjectClass();
+
+        public boolean isAlwaysReauthenticate() {
+            return alwaysReauthenticate;
+        }
+
+        public boolean isRejectPublicInvocations() {
+            return rejectPublicInvocations;
+        }
 
-        throw exception;
+        public boolean isValidateConfigAttributes() {
+            return validateConfigAttributes;
+        }
+
+        public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
+
+        public void setAccessDecisionManager(
+            AccessDecisionManager accessDecisionManager) {
+            this.accessDecisionManager = accessDecisionManager;
+        }
+
+        public void setAfterInvocationManager(
+            AfterInvocationManager afterInvocationManager) {
+            this.afterInvocationManager = afterInvocationManager;
+        }
+
+        /**
+         * Indicates whether the <code>AbstractSecurityInterceptor</code>
+         * should ignore the {@link Authentication#isAuthenticated()}
+         * property. Defaults to <code>false</code>, meaning by default the
+         * <code>Authentication.isAuthenticated()</code> property is trusted
+         * and re-authentication will not occur if the principal has already
+         * been authenticated.
+         *
+         * @param alwaysReauthenticate <code>true</code> to force
+         *        <code>AbstractSecurityInterceptor</code> to disregard the
+         *        value of <code>Authentication.isAuthenticated()</code> and
+         *        always re-authenticate the request (defaults to
+         *        <code>false</code>).
+         */
+        public void setAlwaysReauthenticate(boolean alwaysReauthenticate) {
+            this.alwaysReauthenticate = alwaysReauthenticate;
+        }
+
+        public void setApplicationEventPublisher(
+            ApplicationEventPublisher eventPublisher) {
+            this.eventPublisher = eventPublisher;
+        }
+
+        public void setAuthenticationManager(AuthenticationManager newManager) {
+            this.authenticationManager = newManager;
+        }
+
+        public void setMessageSource(MessageSource messageSource) {
+            this.messages = new MessageSourceAccessor(messageSource);
+        }
+
+        /**
+         * By rejecting public invocations (and setting this property to
+         * <code>true</code>), essentially you are ensuring that every secure
+         * object invocation advised by
+         * <code>AbstractSecurityInterceptor</code> has a configuration
+         * attribute defined. This is useful to ensure a "fail safe" mode
+         * where undeclared secure objects will be rejected and configuration
+         * omissions detected early. An <code>IllegalArgumentException</code>
+         * will be thrown by the <code>AbstractSecurityInterceptor</code> if
+         * you set this property to <code>true</code> and an attempt is made
+         * to invoke a secure object that has no configuration attributes.
+         *
+         * @param rejectPublicInvocations set to <code>true</code> to reject
+         *        invocations of secure objects that have no configuration
+         *        attributes (by default it is <code>true</code> which treats
+         *        undeclared secure objects as "public" or unauthorized)
+         */
+        public void setRejectPublicInvocations(boolean rejectPublicInvocations) {
+            this.rejectPublicInvocations = rejectPublicInvocations;
+        }
+
+        public void setRunAsManager(RunAsManager runAsManager) {
+            this.runAsManager = runAsManager;
+        }
+
+        public void setValidateConfigAttributes(
+            boolean validateConfigAttributes) {
+            this.validateConfigAttributes = validateConfigAttributes;
+        }
     }
-}

+ 39 - 0
core/src/main/java/org/acegisecurity/messages.properties

@@ -0,0 +1,39 @@
+AuthByAdapterProvider.incorrectKey=The presented AuthByAdapter implementation does not contain the expected key
+BasicAclEntryAfterInvocationProvider.noPermission=Authentication {0} has NO permissions at all to the domain object {1}
+BasicAclEntryAfterInvocationProvider.insufficientPermission=Authentication {0} has ACL permissions to the domain object, but not the required ACL permission to the domain object {1}
+ConcurrentSessionControllerImpl.exceededAllowed=Maximum sessions of {0} for this principal exceeded
+ProviderManager.providerNotFound=No AuthenticationProvider found for {0}
+AnonymousAuthenticationProvider.incorrectKey=The presented AnonymousAuthenticationToken does not contain the expected key
+CasAuthenticationProvider.incorrectKey=The presented CasAuthenticationToken does not contain the expected key
+CasAuthenticationProvider.noServiceTicket=Failed to provide a CAS service ticket to validate
+NamedCasProxyDecider.untrusted=Nearest proxy {0} is untrusted
+RejectProxyTickets.reject=Proxy tickets are rejected
+AbstractSecurityInterceptor.authenticationNotFound=An Authentication object was not found in the SecurityContext
+AbstractUserDetailsAuthenticationProvider.onlySupports=Only UsernamePasswordAuthenticationToken is supported
+AbstractUserDetailsAuthenticationProvider.locked=User account is locked
+AbstractUserDetailsAuthenticationProvider.disabled=User is disabled
+AbstractUserDetailsAuthenticationProvider.expired=User account has expired
+AbstractUserDetailsAuthenticationProvider.credentialsExpired=User credentials have expired
+AbstractUserDetailsAuthenticationProvider.badCredentials=Bad credentials
+X509AuthenticationProvider.certificateNull=Certificate is null
+DaoX509AuthoritiesPopulator.noMatching=No matching pattern was found in subjectDN: {0}
+RememberMeAuthenticationProvider.incorrectKey=The presented RememberMeAuthenticationToken does not contain the expected key
+RunAsImplAuthenticationProvider.incorrectKey=The presented RunAsUserToken does not contain the expected key
+DigestProcessingFilter.missingMandatory=Missing mandatory digest value; received header {0}
+DigestProcessingFilter.missingAuth=Missing mandatory digest value for 'auth' QOP; received header {0}
+DigestProcessingFilter.incorrectRealm=Response realm name {0} does not match system realm name of {1}
+DigestProcessingFilter.nonceExpired=Nonce has expired/timed out
+DigestProcessingFilter.nonceEncoding=Nonce is not encoded in Base64; received nonce {0}
+DigestProcessingFilter.nonceNotTwoTokens=Nonce should have yielded two tokens but was {0}
+DigestProcessingFilter.nonceNotNumeric=Nonce token should have yielded a numeric first token, but was {0}
+DigestProcessingFilter.nonceCompromised=Nonce token compromised {0}
+DigestProcessingFilter.usernameNotFound=Username {0} not found
+DigestProcessingFilter.incorrectResponse=Incorrect response
+SwitchUserProcessingFilter.noCurrentUser=No current user associated with this request
+SwitchUserProcessingFilter.noOriginalAuthentication=Could not find original Authentication object
+SwitchUserProcessingFilter.usernameNotFound=Username {0} not found
+SwitchUserProcessingFilter.locked=User account is locked
+SwitchUserProcessingFilter.disabled=User is disabled
+SwitchUserProcessingFilter.expired=User account has expired
+SwitchUserProcessingFilter.credentialsExpired=User credentials have expired
+AbstractAccessDecisionManager.accessDenied=Access is denied

+ 93 - 84
core/src/main/java/org/acegisecurity/providers/ProviderManager.java

@@ -24,9 +24,11 @@ import org.acegisecurity.BadCredentialsException;
 import org.acegisecurity.CredentialsExpiredException;
 import org.acegisecurity.DisabledException;
 import org.acegisecurity.LockedException;
+
 import org.acegisecurity.concurrent.ConcurrentLoginException;
 import org.acegisecurity.concurrent.ConcurrentSessionController;
 import org.acegisecurity.concurrent.NullConcurrentSessionController;
+
 import org.acegisecurity.event.authentication.AbstractAuthenticationEvent;
 import org.acegisecurity.event.authentication.AuthenticationFailureBadCredentialsEvent;
 import org.acegisecurity.event.authentication.AuthenticationFailureConcurrentLoginEvent;
@@ -38,6 +40,7 @@ import org.acegisecurity.event.authentication.AuthenticationFailureProviderNotFo
 import org.acegisecurity.event.authentication.AuthenticationFailureProxyUntrustedEvent;
 import org.acegisecurity.event.authentication.AuthenticationFailureServiceExceptionEvent;
 import org.acegisecurity.event.authentication.AuthenticationSuccessEvent;
+
 import org.acegisecurity.providers.cas.ProxyUntrustedException;
 import org.acegisecurity.providers.dao.UsernameNotFoundException;
 
@@ -48,6 +51,9 @@ import org.springframework.beans.factory.InitializingBean;
 
 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.util.Assert;
 
@@ -85,8 +91,8 @@ import java.util.Properties;
  * If a valid <code>Authentication</code> is returned by an
  * <code>AuthenticationProvider</code>, the <code>ProviderManager</code> will
  * publish an {@link
- * org.acegisecurity.event.authentication.AuthenticationSuccessEvent}. If
- * an <code>AuthenticationException</code> is detected, the final
+ * org.acegisecurity.event.authentication.AuthenticationSuccessEvent}. If an
+ * <code>AuthenticationException</code> is detected, the final
  * <code>AuthenticationException</code> thrown will be used to publish an
  * appropriate failure event. By default <code>ProviderManager</code> maps
  * common exceptions to events, but this can be fine-tuned by providing a new
@@ -98,15 +104,11 @@ import java.util.Properties;
  * and provides its constructor.
  * </p>
  *
- * @author Ben Alex
- * @author Wesley Hall
- * @author Ray Krueger
- * @version $Id$
- *
  * @see ConcurrentSessionController
  */
 public class ProviderManager extends AbstractAuthenticationManager
-    implements InitializingBean, ApplicationEventPublisherAware {
+    implements InitializingBean, ApplicationEventPublisherAware,
+        MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(ProviderManager.class);
@@ -116,74 +118,14 @@ public class ProviderManager extends AbstractAuthenticationManager
     private ApplicationEventPublisher applicationEventPublisher;
     private ConcurrentSessionController sessionController = new NullConcurrentSessionController();
     private List providers;
+    protected MessageSourceAccessor messages;
     private Properties exceptionMappings;
 
     //~ Methods ================================================================
 
-    public void setApplicationEventPublisher(
-        ApplicationEventPublisher applicationEventPublisher) {
-        this.applicationEventPublisher = applicationEventPublisher;
-    }
-
-    /**
-     * Sets the {@link AuthenticationProvider} objects to be used for
-     * authentication.
-     *
-     * @param newList
-     *
-     * @throws IllegalArgumentException DOCUMENT ME!
-     */
-    public void setProviders(List newList) {
-        checkIfValidList(newList);
-
-        Iterator iter = newList.iterator();
-
-        while (iter.hasNext()) {
-            Object currentObject = null;
-
-            try {
-                currentObject = iter.next();
-
-                AuthenticationProvider attemptToCast = (AuthenticationProvider) currentObject;
-            } catch (ClassCastException cce) {
-                throw new IllegalArgumentException("AuthenticationProvider "
-                    + currentObject.getClass().getName()
-                    + " must implement AuthenticationProvider");
-            }
-        }
-
-        this.providers = newList;
-    }
-
-    public List getProviders() {
-        return this.providers;
-    }
-
-    /**
-     * Set the {@link ConcurrentSessionController} to be used for limiting
-     * user's sessions.  The {@link NullConcurrentSessionController} is used
-     * by default
-     *
-     * @param sessionController {@link ConcurrentSessionController}
-     */
-    public void setSessionController(
-        ConcurrentSessionController sessionController) {
-        this.sessionController = sessionController;
-    }
-
-    /**
-     * The configured {@link ConcurrentSessionController} is returned or the
-     * {@link NullConcurrentSessionController} if a specific one has not been
-     * set.
-     *
-     * @return {@link ConcurrentSessionController} instance
-     */
-    public ConcurrentSessionController getSessionController() {
-        return sessionController;
-    }
-
     public void afterPropertiesSet() throws Exception {
         checkIfValidList(this.providers);
+        Assert.notNull(this.messages, "A message source must be set");
 
         if (exceptionMappings == null) {
             exceptionMappings = new Properties();
@@ -211,6 +153,23 @@ public class ProviderManager extends AbstractAuthenticationManager
         }
     }
 
+    private void checkIfValidList(List listToCheck) {
+        if ((listToCheck == null) || (listToCheck.size() == 0)) {
+            throw new IllegalArgumentException(
+                "A list of AuthenticationManagers is required");
+        }
+    }
+
+    /**
+     * Provided so subclasses can add extra exception mappings during startup
+     * if no exception mappings are injected by the IoC container.
+     *
+     * @param exceptionMappings the properties object, which already has
+     *        entries in it
+     */
+    protected void doAddExtraDefaultExceptionMappings(
+        Properties exceptionMappings) {}
+
     /**
      * Attempts to authenticate the passed {@link Authentication} object.
      * 
@@ -244,8 +203,7 @@ public class ProviderManager extends AbstractAuthenticationManager
         AuthenticationException lastException = null;
 
         while (iter.hasNext()) {
-            AuthenticationProvider provider = (AuthenticationProvider) iter
-                .next();
+            AuthenticationProvider provider = (AuthenticationProvider) iter.next();
 
             if (provider.supports(toTest)) {
                 logger.debug("Authentication attempt using "
@@ -272,8 +230,10 @@ public class ProviderManager extends AbstractAuthenticationManager
         }
 
         if (lastException == null) {
-            lastException = new ProviderNotFoundException(
-                    "No authentication provider for " + toTest.getName());
+            lastException = new ProviderNotFoundException(messages.getMessage(
+                        "ProviderManager.providerNotFound",
+                        new Object[] {toTest.getName()},
+                        "No AuthenticationProvider found for {0}"));
         }
 
         // Publish the event
@@ -309,20 +269,69 @@ public class ProviderManager extends AbstractAuthenticationManager
         throw lastException;
     }
 
+    public List getProviders() {
+        return this.providers;
+    }
+
     /**
-     * Provided so subclasses can add extra exception mappings during startup
-     * if no exception mappings are injected by the IoC container.
+     * The configured {@link ConcurrentSessionController} is returned or the
+     * {@link NullConcurrentSessionController} if a specific one has not been
+     * set.
      *
-     * @param exceptionMappings the properties object, which already has
-     *        entries in it
+     * @return {@link ConcurrentSessionController} instance
      */
-    protected void doAddExtraDefaultExceptionMappings(
-        Properties exceptionMappings) {}
+    public ConcurrentSessionController getSessionController() {
+        return sessionController;
+    }
 
-    private void checkIfValidList(List listToCheck) {
-        if ((listToCheck == null) || (listToCheck.size() == 0)) {
-            throw new IllegalArgumentException(
-                "A list of AuthenticationManagers is required");
+    public void setApplicationEventPublisher(
+        ApplicationEventPublisher applicationEventPublisher) {
+        this.applicationEventPublisher = applicationEventPublisher;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
+    /**
+     * Sets the {@link AuthenticationProvider} objects to be used for
+     * authentication.
+     *
+     * @param newList
+     *
+     * @throws IllegalArgumentException DOCUMENT ME!
+     */
+    public void setProviders(List newList) {
+        checkIfValidList(newList);
+
+        Iterator iter = newList.iterator();
+
+        while (iter.hasNext()) {
+            Object currentObject = null;
+
+            try {
+                currentObject = iter.next();
+
+                AuthenticationProvider attemptToCast = (AuthenticationProvider) currentObject;
+            } catch (ClassCastException cce) {
+                throw new IllegalArgumentException("AuthenticationProvider "
+                    + currentObject.getClass().getName()
+                    + " must implement AuthenticationProvider");
+            }
         }
+
+        this.providers = newList;
+    }
+
+    /**
+     * Set the {@link ConcurrentSessionController} to be used for limiting
+     * user's sessions.  The {@link NullConcurrentSessionController} is used
+     * by default
+     *
+     * @param sessionController {@link ConcurrentSessionController}
+     */
+    public void setSessionController(
+        ConcurrentSessionController sessionController) {
+        this.sessionController = sessionController;
     }
 }

+ 24 - 15
core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProvider.java

@@ -18,6 +18,7 @@ package org.acegisecurity.providers.anonymous;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.BadCredentialsException;
+
 import org.acegisecurity.providers.AuthenticationProvider;
 
 import org.apache.commons.logging.Log;
@@ -25,6 +26,10 @@ import org.apache.commons.logging.LogFactory;
 
 import org.springframework.beans.factory.InitializingBean;
 
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 
@@ -37,32 +42,23 @@ import org.springframework.util.Assert;
  * org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken#getKeyHash()}
  * must match this class' {@link #getKey()}.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class AnonymousAuthenticationProvider implements AuthenticationProvider,
-    InitializingBean {
+    InitializingBean, MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(AnonymousAuthenticationProvider.class);
 
     //~ Instance fields ========================================================
 
+    protected MessageSourceAccessor messages;
     private String key;
 
     //~ Methods ================================================================
 
-    public void setKey(String key) {
-        this.key = key;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
     public void afterPropertiesSet() throws Exception {
-        Assert.hasLength(key);
+        Assert.hasLength(key, "A Key is required");
+        Assert.notNull(this.messages, "A message source must be set");
     }
 
     public Authentication authenticate(Authentication authentication)
@@ -73,13 +69,26 @@ public class AnonymousAuthenticationProvider implements AuthenticationProvider,
 
         if (this.key.hashCode() != ((AnonymousAuthenticationToken) authentication)
             .getKeyHash()) {
-            throw new BadCredentialsException(
-                "The presented AnonymousAuthenticationToken does not contain the expected key");
+            throw new BadCredentialsException(messages.getMessage(
+                    "AnonymousAuthenticationProvider.incorrectKey",
+                    "The presented AnonymousAuthenticationToken does not contain the expected key"));
         }
 
         return authentication;
     }
 
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
     public boolean supports(Class authentication) {
         return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));
     }

+ 80 - 65
core/src/main/java/org/acegisecurity/providers/cas/CasAuthenticationProvider.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -19,14 +19,21 @@ import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.BadCredentialsException;
 import org.acegisecurity.UserDetails;
+
 import org.acegisecurity.providers.AuthenticationProvider;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
 import org.acegisecurity.ui.cas.CasProcessingFilter;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 
@@ -42,12 +49,9 @@ import org.springframework.util.Assert;
  * CasProcessingFilter#CAS_STATELESS_IDENTIFIER}. It can also validate a
  * previously created {@link CasAuthenticationToken}.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class CasAuthenticationProvider implements AuthenticationProvider,
-    InitializingBean {
+    InitializingBean, MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
@@ -56,60 +60,23 @@ public class CasAuthenticationProvider implements AuthenticationProvider,
 
     private CasAuthoritiesPopulator casAuthoritiesPopulator;
     private CasProxyDecider casProxyDecider;
+    protected MessageSourceAccessor messages;
     private StatelessTicketCache statelessTicketCache;
     private String key;
     private TicketValidator ticketValidator;
 
     //~ Methods ================================================================
 
-    public void setCasAuthoritiesPopulator(
-        CasAuthoritiesPopulator casAuthoritiesPopulator) {
-        this.casAuthoritiesPopulator = casAuthoritiesPopulator;
-    }
-
-    public CasAuthoritiesPopulator getCasAuthoritiesPopulator() {
-        return casAuthoritiesPopulator;
-    }
-
-    public void setCasProxyDecider(CasProxyDecider casProxyDecider) {
-        this.casProxyDecider = casProxyDecider;
-    }
-
-    public CasProxyDecider getCasProxyDecider() {
-        return casProxyDecider;
-    }
-
-    public void setKey(String key) {
-        this.key = key;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
-    public void setStatelessTicketCache(
-        StatelessTicketCache statelessTicketCache) {
-        this.statelessTicketCache = statelessTicketCache;
-    }
-
-    public StatelessTicketCache getStatelessTicketCache() {
-        return statelessTicketCache;
-    }
-
-    public void setTicketValidator(TicketValidator ticketValidator) {
-        this.ticketValidator = ticketValidator;
-    }
-
-    public TicketValidator getTicketValidator() {
-        return ticketValidator;
-    }
-
     public void afterPropertiesSet() throws Exception {
-        Assert.notNull(this.casAuthoritiesPopulator, "A casAuthoritiesPopulator must be set");
+        Assert.notNull(this.casAuthoritiesPopulator,
+            "A casAuthoritiesPopulator must be set");
         Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
         Assert.notNull(this.casProxyDecider, "A casProxyDecider must be set");
-        Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
-        Assert.notNull(key, "A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
+        Assert.notNull(this.statelessTicketCache,
+            "A statelessTicketCache must be set");
+        Assert.notNull(key,
+            "A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
+        Assert.notNull(this.messages, "A message source must be set");
     }
 
     public Authentication authenticate(Authentication authentication)
@@ -133,16 +100,18 @@ public class CasAuthenticationProvider implements AuthenticationProvider,
                 .getKeyHash()) {
                 return authentication;
             } else {
-                throw new BadCredentialsException(
-                    "The presented CasAuthenticationToken does not contain the expected key");
+                throw new BadCredentialsException(messages.getMessage(
+                        "CasAuthenticationProvider.incorrectKey",
+                        "The presented CasAuthenticationToken does not contain the expected key"));
             }
         }
 
         // Ensure credentials are presented
         if ((authentication.getCredentials() == null)
             || "".equals(authentication.getCredentials())) {
-            throw new BadCredentialsException(
-                "Failed to provide a CAS service ticket to validate");
+            throw new BadCredentialsException(messages.getMessage(
+                    "CasAuthenticationProvider.noServiceTicket",
+                    "Failed to provide a CAS service ticket to validate"));
         }
 
         boolean stateless = false;
@@ -173,17 +142,6 @@ public class CasAuthenticationProvider implements AuthenticationProvider,
         return result;
     }
 
-    public boolean supports(Class authentication) {
-        if (UsernamePasswordAuthenticationToken.class.isAssignableFrom(
-                authentication)) {
-            return true;
-        } else if (CasAuthenticationToken.class.isAssignableFrom(authentication)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
     private CasAuthenticationToken authenticateNow(
         Authentication authentication) throws AuthenticationException {
         // Validate
@@ -203,4 +161,61 @@ public class CasAuthenticationProvider implements AuthenticationProvider,
             userDetails, response.getProxyList(),
             response.getProxyGrantingTicketIou());
     }
+
+    public CasAuthoritiesPopulator getCasAuthoritiesPopulator() {
+        return casAuthoritiesPopulator;
+    }
+
+    public CasProxyDecider getCasProxyDecider() {
+        return casProxyDecider;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public StatelessTicketCache getStatelessTicketCache() {
+        return statelessTicketCache;
+    }
+
+    public TicketValidator getTicketValidator() {
+        return ticketValidator;
+    }
+
+    public void setCasAuthoritiesPopulator(
+        CasAuthoritiesPopulator casAuthoritiesPopulator) {
+        this.casAuthoritiesPopulator = casAuthoritiesPopulator;
+    }
+
+    public void setCasProxyDecider(CasProxyDecider casProxyDecider) {
+        this.casProxyDecider = casProxyDecider;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
+    public void setStatelessTicketCache(
+        StatelessTicketCache statelessTicketCache) {
+        this.statelessTicketCache = statelessTicketCache;
+    }
+
+    public void setTicketValidator(TicketValidator ticketValidator) {
+        this.ticketValidator = ticketValidator;
+    }
+
+    public boolean supports(Class authentication) {
+        if (UsernamePasswordAuthenticationToken.class.isAssignableFrom(
+                authentication)) {
+            return true;
+        } else if (CasAuthenticationToken.class.isAssignableFrom(authentication)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
 }

+ 26 - 15
core/src/main/java/org/acegisecurity/providers/cas/proxy/NamedCasProxyDecider.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -22,6 +22,11 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 import java.util.List;
@@ -35,11 +40,9 @@ import java.util.List;
  * Also accepts the request if there was no proxy (ie the user directly
  * authenticated against this service).
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
-public class NamedCasProxyDecider implements CasProxyDecider, InitializingBean {
+public class NamedCasProxyDecider implements CasProxyDecider, InitializingBean,
+    MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(NamedCasProxyDecider.class);
@@ -47,19 +50,13 @@ public class NamedCasProxyDecider implements CasProxyDecider, InitializingBean {
     //~ Instance fields ========================================================
 
     private List validProxies;
+    protected MessageSourceAccessor messages;
 
     //~ Methods ================================================================
 
-    public void setValidProxies(List validProxies) {
-        this.validProxies = validProxies;
-    }
-
-    public List getValidProxies() {
-        return validProxies;
-    }
-
     public void afterPropertiesSet() throws Exception {
         Assert.notNull(this.validProxies, "A validProxies list must be set");
+        Assert.notNull(this.messages, "A message source must be set");
     }
 
     public void confirmProxyListTrusted(List proxyList)
@@ -76,8 +73,22 @@ public class NamedCasProxyDecider implements CasProxyDecider, InitializingBean {
         }
 
         if (!validProxies.contains(proxyList.get(0))) {
-            throw new ProxyUntrustedException("Nearest proxy '"
-                    + proxyList.get(0) + "' is untrusted");
+            throw new ProxyUntrustedException(messages.getMessage(
+                    "NamedCasProxyDecider.untrusted",
+                    new Object[] {proxyList.get(0)},
+                    "Nearest proxy {0} is untrusted"));
         }
     }
+
+    public List getValidProxies() {
+        return validProxies;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
+    public void setValidProxies(List validProxies) {
+        this.validProxies = validProxies;
+    }
 }

+ 25 - 7
core/src/main/java/org/acegisecurity/providers/cas/proxy/RejectProxyTickets.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -20,6 +20,13 @@ import org.acegisecurity.providers.cas.ProxyUntrustedException;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 import java.util.List;
@@ -32,17 +39,23 @@ import java.util.List;
  * This class should be used if only service tickets wish to be accepted (ie no
  * proxy tickets at all).
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
-public class RejectProxyTickets implements CasProxyDecider {
+public class RejectProxyTickets implements CasProxyDecider, MessageSourceAware,
+    InitializingBean {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(RejectProxyTickets.class);
 
+    //~ Instance fields ========================================================
+
+    protected MessageSourceAccessor messages;
+
     //~ Methods ================================================================
 
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(this.messages, "A message source must be set");
+    }
+
     public void confirmProxyListTrusted(List proxyList)
         throws ProxyUntrustedException {
         Assert.notNull(proxyList, "proxyList cannot be null");
@@ -54,9 +67,14 @@ public class RejectProxyTickets implements CasProxyDecider {
 
         if (logger.isDebugEnabled()) {
             logger.debug("Proxies are unacceptable; proxy list provided: "
-                    + proxyList.toString());
+                + proxyList.toString());
         }
 
-        throw new ProxyUntrustedException("Proxy tickets are rejected");
+        throw new ProxyUntrustedException(messages.getMessage(
+                "RejectProxyTickets.reject", "Proxy tickets are rejected"));
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
     }
 }

+ 97 - 80
core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java

@@ -22,12 +22,17 @@ import org.acegisecurity.CredentialsExpiredException;
 import org.acegisecurity.DisabledException;
 import org.acegisecurity.LockedException;
 import org.acegisecurity.UserDetails;
+
 import org.acegisecurity.providers.AuthenticationProvider;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.providers.dao.cache.NullUserCache;
 
 import org.springframework.beans.factory.InitializingBean;
 
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 
@@ -62,37 +67,44 @@ import org.springframework.util.Assert;
  * incorrect password, the {@link AuthenticationDao} will be queried to
  * confirm the most up-to-date password was used for comparison.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public abstract class AbstractUserDetailsAuthenticationProvider
-    implements AuthenticationProvider, InitializingBean {
+    implements AuthenticationProvider, InitializingBean, MessageSourceAware {
     //~ Instance fields ========================================================
 
+    protected MessageSourceAccessor messages;
     private UserCache userCache = new NullUserCache();
     private boolean forcePrincipalAsString = false;
 
     //~ Methods ================================================================
 
-    public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
-        this.forcePrincipalAsString = forcePrincipalAsString;
-    }
-
-    public boolean isForcePrincipalAsString() {
-        return forcePrincipalAsString;
-    }
-
-    public void setUserCache(UserCache userCache) {
-        this.userCache = userCache;
-    }
-
-    public UserCache getUserCache() {
-        return userCache;
-    }
+    /**
+     * Allows subclasses to perform any additional checks of a returned (or
+     * cached) <code>UserDetails</code> for a given authentication request.
+     * Generally a subclass will at least compare the {@link
+     * Authentication#getCredentials()} with a {@link
+     * UserDetails#getPassword()}. If custom logic is needed to compare
+     * additional properties of <code>UserDetails</code> and/or
+     * <code>UsernamePasswordAuthenticationToken</code>, these should also
+     * appear in this method.
+     *
+     * @param userDetails as retrieved from the {@link #retrieveUser(String,
+     *        UsernamePasswordAuthenticationToken)} or <code>UserCache</code>
+     * @param authentication the current request that needs to be authenticated
+     *
+     * @throws AuthenticationException AuthenticationException if the
+     *         credentials could not be validated (generally a
+     *         <code>BadCredentialsException</code>, an
+     *         <code>AuthenticationServiceException</code>)
+     */
+    protected abstract void additionalAuthenticationChecks(
+        UserDetails userDetails,
+        UsernamePasswordAuthenticationToken authentication)
+        throws AuthenticationException;
 
     public final void afterPropertiesSet() throws Exception {
         Assert.notNull(this.userCache, "A user cache must be set");
+        Assert.notNull(this.messages, "A message source must be set");
         doAfterPropertiesSet();
     }
 
@@ -100,7 +112,9 @@ public abstract class AbstractUserDetailsAuthenticationProvider
         throws AuthenticationException {
         Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class,
             authentication,
-            "Only UsernamePasswordAuthenticationToken is supported");
+            messages.getMessage(
+                "AbstractUserDetailsAuthenticationProvider.onlySupports",
+                "Only UsernamePasswordAuthenticationToken is supported"));
 
         // Determine username
         String username = (authentication.getPrincipal() == null)
@@ -118,15 +132,21 @@ public abstract class AbstractUserDetailsAuthenticationProvider
         }
 
         if (!user.isAccountNonLocked()) {
-            throw new LockedException("User account is locked");
+            throw new LockedException(messages.getMessage(
+                    "AbstractUserDetailsAuthenticationProvider.locked",
+                    "User account is locked"));
         }
 
         if (!user.isEnabled()) {
-            throw new DisabledException("User is disabled");
+            throw new DisabledException(messages.getMessage(
+                    "AbstractUserDetailsAuthenticationProvider.disabled",
+                    "User is disabled"));
         }
 
         if (!user.isAccountNonExpired()) {
-            throw new AccountExpiredException("User account has expired");
+            throw new AccountExpiredException(messages.getMessage(
+                    "AbstractUserDetailsAuthenticationProvider.expired",
+                    "User account has expired"));
         }
 
         // This check must come here, as we don't want to tell users
@@ -144,8 +164,9 @@ public abstract class AbstractUserDetailsAuthenticationProvider
         }
 
         if (!user.isCredentialsNonExpired()) {
-            throw new CredentialsExpiredException(
-                "User credentials have expired");
+            throw new CredentialsExpiredException(messages.getMessage(
+                    "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
+                    "User credentials have expired"));
         }
 
         if (!cacheWasUsed) {
@@ -162,36 +183,51 @@ public abstract class AbstractUserDetailsAuthenticationProvider
             user);
     }
 
-    public boolean supports(Class authentication) {
-        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
-    }
-
     /**
-     * Allows subclasses to perform any additional checks of a returned (or
-     * cached) <code>UserDetails</code> for a given authentication request.
-     * Generally a subclass will at least compare the {@link
-     * Authentication#getCredentials()} with a {@link
-     * UserDetails#getPassword()}. If custom logic is needed to compare
-     * additional properties of <code>UserDetails</code> and/or
-     * <code>UsernamePasswordAuthenticationToken</code>, these should also
-     * appear in this method.
+     * Creates a successful {@link Authentication} object.
+     * 
+     * <P>
+     * Protected so subclasses can override.
+     * </p>
+     * 
+     * <P>
+     * Subclasses will usually store the original credentials the user supplied
+     * (not salted or encoded passwords) in the returned
+     * <code>Authentication</code> object.
+     * </p>
      *
-     * @param userDetails as retrieved from the {@link #retrieveUser(String,
-     *        UsernamePasswordAuthenticationToken)} or <code>UserCache</code>
-     * @param authentication the current request that needs to be authenticated
+     * @param principal that should be the principal in the returned object
+     *        (defined by the {@link #isForcePrincipalAsString()} method)
+     * @param authentication that was presented to the
+     *        <code>DaoAuthenticationProvider</code> for validation
+     * @param user that was loaded by the <code>AuthenticationDao</code>
      *
-     * @throws AuthenticationException AuthenticationException if the
-     *         credentials could not be validated (generally a
-     *         <code>BadCredentialsException</code>, an
-     *         <code>AuthenticationServiceException</code>)
+     * @return the successful authentication token
      */
-    protected abstract void additionalAuthenticationChecks(
-        UserDetails userDetails,
-        UsernamePasswordAuthenticationToken authentication)
-        throws AuthenticationException;
+    protected Authentication createSuccessAuthentication(Object principal,
+        Authentication authentication, UserDetails user) {
+        // Ensure we return the original credentials the user supplied,
+        // so subsequent attempts are successful even with encoded passwords.
+        // Also ensure we return the original getDetails(), so that future
+        // authentication events after cache expiry contain the details
+        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
+                authentication.getCredentials(), user.getAuthorities());
+        result.setDetails((authentication.getDetails() != null)
+            ? authentication.getDetails() : null);
+
+        return result;
+    }
 
     protected void doAfterPropertiesSet() throws Exception {}
 
+    public UserCache getUserCache() {
+        return userCache;
+    }
+
+    public boolean isForcePrincipalAsString() {
+        return forcePrincipalAsString;
+    }
+
     /**
      * Allows subclasses to actually retrieve the <code>UserDetails</code> from
      * an implementation-specific location, with the option of throwing an
@@ -243,38 +279,19 @@ public abstract class AbstractUserDetailsAuthenticationProvider
         UsernamePasswordAuthenticationToken authentication)
         throws AuthenticationException;
 
-    /**
-     * Creates a successful {@link Authentication} object.
-     * 
-     * <P>
-     * Protected so subclasses can override.
-     * </p>
-     * 
-     * <P>
-     * Subclasses will usually store the original credentials the user supplied
-     * (not salted or encoded passwords) in the returned
-     * <code>Authentication</code> object.
-     * </p>
-     *
-     * @param principal that should be the principal in the returned object
-     *        (defined by the {@link #isForcePrincipalAsString()} method)
-     * @param authentication that was presented to the
-     *        <code>DaoAuthenticationProvider</code> for validation
-     * @param user that was loaded by the <code>AuthenticationDao</code>
-     *
-     * @return the successful authentication token
-     */
-    protected Authentication createSuccessAuthentication(Object principal,
-        Authentication authentication, UserDetails user) {
-        // Ensure we return the original credentials the user supplied,
-        // so subsequent attempts are successful even with encoded passwords.
-        // Also ensure we return the original getDetails(), so that future
-        // authentication events after cache expiry contain the details
-        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
-                authentication.getCredentials(), user.getAuthorities());
-        result.setDetails((authentication.getDetails() != null)
-            ? authentication.getDetails() : null);
+    public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
+        this.forcePrincipalAsString = forcePrincipalAsString;
+    }
 
-        return result;
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
+    public void setUserCache(UserCache userCache) {
+        this.userCache = userCache;
+    }
+
+    public boolean supports(Class authentication) {
+        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
     }
 }

+ 77 - 73
core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java

@@ -19,6 +19,7 @@ import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.AuthenticationServiceException;
 import org.acegisecurity.BadCredentialsException;
 import org.acegisecurity.UserDetails;
+
 import org.acegisecurity.providers.AuthenticationProvider;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.providers.encoding.PasswordEncoder;
@@ -32,9 +33,6 @@ import org.springframework.util.Assert;
 /**
  * An {@link AuthenticationProvider} implementation that retrieves user details
  * from an {@link AuthenticationDao}.
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class DaoAuthenticationProvider
     extends AbstractUserDetailsAuthenticationProvider {
@@ -47,68 +45,6 @@ public class DaoAuthenticationProvider
 
     //~ Methods ================================================================
 
-    public void setAuthenticationDao(AuthenticationDao authenticationDao) {
-        this.authenticationDao = authenticationDao;
-    }
-
-    public AuthenticationDao getAuthenticationDao() {
-        return authenticationDao;
-    }
-
-    /**
-     * By default the <code>DaoAuthenticationProvider</code> throws a
-     * <code>BadCredentialsException</code> if a username is not found or the
-     * password is incorrect. Setting this property to <code>false</code> will
-     * cause <code>UsernameNotFoundException</code>s to be thrown instead for
-     * the former. Note this is considered less secure than throwing
-     * <code>BadCredentialsException</code> for both exceptions.
-     *
-     * @param hideUserNotFoundExceptions set to <code>false</code> if you wish
-     *        <code>UsernameNotFoundException</code>s to be thrown instead of
-     *        the non-specific <code>BadCredentialsException</code> (defaults
-     *        to <code>true</code>)
-     */
-    public void setHideUserNotFoundExceptions(
-        boolean hideUserNotFoundExceptions) {
-        this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
-    }
-
-    public boolean isHideUserNotFoundExceptions() {
-        return hideUserNotFoundExceptions;
-    }
-
-    /**
-     * Sets the PasswordEncoder instance to be used to encode and validate
-     * passwords. If not set, {@link PlaintextPasswordEncoder} will be used by
-     * default.
-     *
-     * @param passwordEncoder The passwordEncoder to use
-     */
-    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
-        this.passwordEncoder = passwordEncoder;
-    }
-
-    public PasswordEncoder getPasswordEncoder() {
-        return passwordEncoder;
-    }
-
-    /**
-     * The source of salts to use when decoding passwords.  <code>null</code>
-     * is a valid value, meaning the <code>DaoAuthenticationProvider</code>
-     * will present <code>null</code> to the relevant
-     * <code>PasswordEncoder</code>.
-     *
-     * @param saltSource to use when attempting to decode passwords via  the
-     *        <code>PasswordEncoder</code>
-     */
-    public void setSaltSource(SaltSource saltSource) {
-        this.saltSource = saltSource;
-    }
-
-    public SaltSource getSaltSource() {
-        return saltSource;
-    }
-
     protected void additionalAuthenticationChecks(UserDetails userDetails,
         UsernamePasswordAuthenticationToken authentication)
         throws AuthenticationException {
@@ -120,7 +56,9 @@ public class DaoAuthenticationProvider
 
         if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
                 authentication.getCredentials().toString(), salt)) {
-            throw new BadCredentialsException("Bad credentials", userDetails);
+            throw new BadCredentialsException(messages.getMessage(
+                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
+                    "Bad credentials"), userDetails);
         }
     }
 
@@ -129,6 +67,22 @@ public class DaoAuthenticationProvider
             "An Authentication DAO must be set");
     }
 
+    public AuthenticationDao getAuthenticationDao() {
+        return authenticationDao;
+    }
+
+    public PasswordEncoder getPasswordEncoder() {
+        return passwordEncoder;
+    }
+
+    public SaltSource getSaltSource() {
+        return saltSource;
+    }
+
+    public boolean isHideUserNotFoundExceptions() {
+        return hideUserNotFoundExceptions;
+    }
+
     protected final UserDetails retrieveUser(String username,
         UsernamePasswordAuthenticationToken authentication)
         throws AuthenticationException {
@@ -138,20 +92,70 @@ public class DaoAuthenticationProvider
             loadedUser = this.authenticationDao.loadUserByUsername(username);
         } catch (UsernameNotFoundException notFound) {
             if (hideUserNotFoundExceptions) {
-                throw new BadCredentialsException("Bad credentials presented");
+                throw new BadCredentialsException(messages.getMessage(
+                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
+                        "Bad credentials"));
             } else {
                 throw notFound;
             }
         } catch (DataAccessException repositoryProblem) {
             throw new AuthenticationServiceException(repositoryProblem
-                .getMessage(), repositoryProblem);
+                    .getMessage(), repositoryProblem);
+            }
+
+            if (loadedUser == null) {
+                throw new AuthenticationServiceException(
+                    "AuthenticationDao returned null, which is an interface contract violation");
+            }
+
+            return loadedUser;
+        }
+
+        public void setAuthenticationDao(AuthenticationDao authenticationDao) {
+            this.authenticationDao = authenticationDao;
         }
 
-        if (loadedUser == null) {
-            throw new AuthenticationServiceException(
-                "AuthenticationDao returned null, which is an interface contract violation");
+        /**
+         * By default the <code>DaoAuthenticationProvider</code> throws a
+         * <code>BadCredentialsException</code> if a username is not found or
+         * the password is incorrect. Setting this property to
+         * <code>false</code> will cause
+         * <code>UsernameNotFoundException</code>s to be thrown instead for
+         * the former. Note this is considered less secure than throwing
+         * <code>BadCredentialsException</code> for both exceptions.
+         *
+         * @param hideUserNotFoundExceptions set to <code>false</code> if you
+         *        wish <code>UsernameNotFoundException</code>s to be thrown
+         *        instead of the non-specific
+         *        <code>BadCredentialsException</code> (defaults to
+         *        <code>true</code>)
+         */
+        public void setHideUserNotFoundExceptions(
+            boolean hideUserNotFoundExceptions) {
+            this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
         }
 
-        return loadedUser;
+        /**
+         * Sets the PasswordEncoder instance to be used to encode and validate
+         * passwords. If not set, {@link PlaintextPasswordEncoder} will be
+         * used by default.
+         *
+         * @param passwordEncoder The passwordEncoder to use
+         */
+        public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+            this.passwordEncoder = passwordEncoder;
+        }
+
+        /**
+         * The source of salts to use when decoding passwords.
+         * <code>null</code> is a valid value, meaning the
+         * <code>DaoAuthenticationProvider</code> will present
+         * <code>null</code> to the relevant <code>PasswordEncoder</code>.
+         *
+         * @param saltSource to use when attempting to decode passwords via the
+         *        <code>PasswordEncoder</code>
+         */
+        public void setSaltSource(SaltSource saltSource) {
+            this.saltSource = saltSource;
+        }
     }
-}

+ 23 - 14
core/src/main/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProvider.java

@@ -18,6 +18,7 @@ package org.acegisecurity.providers.rememberme;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.BadCredentialsException;
+
 import org.acegisecurity.providers.AuthenticationProvider;
 
 import org.apache.commons.logging.Log;
@@ -25,6 +26,10 @@ import org.apache.commons.logging.LogFactory;
 
 import org.springframework.beans.factory.InitializingBean;
 
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 
@@ -37,32 +42,23 @@ import org.springframework.util.Assert;
  * org.acegisecurity.providers.rememberme.RememberMeAuthenticationToken#getKeyHash()}
  * must match this class' {@link #getKey()}.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class RememberMeAuthenticationProvider implements AuthenticationProvider,
-    InitializingBean {
+    InitializingBean, MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(RememberMeAuthenticationProvider.class);
 
     //~ Instance fields ========================================================
 
+    protected MessageSourceAccessor messages;
     private String key;
 
     //~ Methods ================================================================
 
-    public void setKey(String key) {
-        this.key = key;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
     public void afterPropertiesSet() throws Exception {
         Assert.hasLength(key);
+        Assert.notNull(this.messages, "A message source must be set");
     }
 
     public Authentication authenticate(Authentication authentication)
@@ -73,13 +69,26 @@ public class RememberMeAuthenticationProvider implements AuthenticationProvider,
 
         if (this.key.hashCode() != ((RememberMeAuthenticationToken) authentication)
             .getKeyHash()) {
-            throw new BadCredentialsException(
-                "The presented RememberMeAuthenticationToken does not contain the expected key");
+            throw new BadCredentialsException(messages.getMessage(
+                    "RememberMeAuthenticationProvider.incorrectKey",
+                    "The presented RememberMeAuthenticationToken does not contain the expected key"));
         }
 
         return authentication;
     }
 
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
     public boolean supports(Class authentication) {
         return (RememberMeAuthenticationToken.class.isAssignableFrom(authentication));
     }

+ 72 - 45
core/src/main/java/org/acegisecurity/providers/x509/X509AuthenticationProvider.java

@@ -15,71 +15,81 @@
 
 package org.acegisecurity.providers.x509;
 
-import org.acegisecurity.providers.AuthenticationProvider;
-import org.acegisecurity.providers.x509.cache.NullX509UserCache;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
-import org.acegisecurity.UserDetails;
 import org.acegisecurity.BadCredentialsException;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.util.Assert;
+import org.acegisecurity.UserDetails;
+
+import org.acegisecurity.providers.AuthenticationProvider;
+import org.acegisecurity.providers.x509.cache.NullX509UserCache;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
+import org.springframework.util.Assert;
+
 import java.security.cert.X509Certificate;
 
+
 /**
  * Processes an X.509 authentication request.
+ * 
  * <p>
- * The request will typically originate from
- * {@link org.acegisecurity.ui.x509.X509ProcessingFilter}).
+ * The request will typically originate from {@link
+ * org.acegisecurity.ui.x509.X509ProcessingFilter}).
  * </p>
- *
- * @author Luke Taylor
- * @version $Id$
  */
 public class X509AuthenticationProvider implements AuthenticationProvider,
-    InitializingBean {
+    InitializingBean, MessageSourceAware {
     //~ Static fields/initializers =============================================
-    
+
     private static final Log logger = LogFactory.getLog(X509AuthenticationProvider.class);
 
     //~ Instance fields ========================================================
 
+    protected MessageSourceAccessor messages;
     private X509AuthoritiesPopulator x509AuthoritiesPopulator;
     private X509UserCache userCache = new NullX509UserCache();
 
     //~ Methods ================================================================
 
-    public void setX509AuthoritiesPopulator(X509AuthoritiesPopulator x509AuthoritiesPopulator) {
-        this.x509AuthoritiesPopulator = x509AuthoritiesPopulator;
-    }
-
-    public void setX509UserCache(X509UserCache cache) {
-        this.userCache = cache;
-    }
-
     public void afterPropertiesSet() throws Exception {
         Assert.notNull(userCache, "An x509UserCache must be set");
-        Assert.notNull(x509AuthoritiesPopulator, "An X509AuthoritiesPopulator must be set");
+        Assert.notNull(x509AuthoritiesPopulator,
+            "An X509AuthoritiesPopulator must be set");
+        Assert.notNull(this.messages, "A message source must be set");
     }
 
     /**
-     * If the supplied authentication token contains a certificate then this will be passed
-     * to the configured {@link X509AuthoritiesPopulator}
-     * to obtain the user details and authorities for the user identified by the certificate.
+     * If the supplied authentication token contains a certificate then this
+     * will be passed to the configured {@link X509AuthoritiesPopulator} to
+     * obtain the user details and authorities for the user identified by the
+     * certificate.
+     * 
      * <p>
-     * If no certificate is present (for example, if the filter is applied to an HttpRequest for which
-     * client authentication hasn't been configured in the container) then a BadCredentialsException will be raised.
+     * If no certificate is present (for example, if the filter is applied to
+     * an HttpRequest for which client authentication hasn't been configured
+     * in the container) then a BadCredentialsException will be raised.
      * </p>
      *
      * @param authentication the authentication request.
-     * @return an X509AuthenticationToken containing the authorities of the principal represented by the
-     * certificate.
-     * @throws AuthenticationException if the {@link X509AuthoritiesPopulator} rejects the certficate.
-     * @throws BadCredentialsException if no certificate was presented in the authentication request.
+     *
+     * @return an X509AuthenticationToken containing the authorities of the
+     *         principal represented by the certificate.
+     *
+     * @throws AuthenticationException if the {@link X509AuthoritiesPopulator}
+     *         rejects the certficate.
+     * @throws BadCredentialsException if no certificate was presented in the
+     *         authentication request.
      */
-    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+    public Authentication authenticate(Authentication authentication)
+        throws AuthenticationException {
         if (!supports(authentication.getClass())) {
             return null;
         }
@@ -88,25 +98,42 @@ public class X509AuthenticationProvider implements AuthenticationProvider,
             logger.debug("X509 authentication request: " + authentication);
         }
 
-        X509Certificate clientCertificate = (X509Certificate)authentication.getCredentials();
+        X509Certificate clientCertificate = (X509Certificate) authentication
+                .getCredentials();
 
-        if(clientCertificate == null) {
-            throw new BadCredentialsException("Certificate is null.");
+            if (clientCertificate == null) {
+                throw new BadCredentialsException(messages.getMessage(
+                        "X509AuthenticationProvider.certificateNull",
+                        "Certificate is null"));
+            }
+
+            UserDetails user = userCache.getUserFromCache(clientCertificate);
+
+            if (user == null) {
+                logger.debug("Authenticating with certificate "
+                    + clientCertificate);
+                user = x509AuthoritiesPopulator.getUserDetails(clientCertificate);
+                userCache.putUserInCache(clientCertificate, user);
+            }
+
+            return new X509AuthenticationToken(user, clientCertificate,
+                user.getAuthorities());
         }
 
-        UserDetails user = userCache.getUserFromCache(clientCertificate);
+        public void setMessageSource(MessageSource messageSource) {
+            this.messages = new MessageSourceAccessor(messageSource);
+        }
 
-        if(user == null) {
-            logger.debug("Authenticating with certificate " + clientCertificate);
-            user = x509AuthoritiesPopulator.getUserDetails(clientCertificate);
-            userCache.putUserInCache(clientCertificate, user);
+        public void setX509AuthoritiesPopulator(
+            X509AuthoritiesPopulator x509AuthoritiesPopulator) {
+            this.x509AuthoritiesPopulator = x509AuthoritiesPopulator;
         }
 
-        return new X509AuthenticationToken(user, clientCertificate, user.getAuthorities());
-    }
+        public void setX509UserCache(X509UserCache cache) {
+            this.userCache = cache;
+        }
 
-    public boolean supports(Class authentication) {
-        return X509AuthenticationToken.class.isAssignableFrom(authentication);
+        public boolean supports(Class authentication) {
+            return X509AuthenticationToken.class.isAssignableFrom(authentication);
+        }
     }
-
-}

+ 62 - 43
core/src/main/java/org/acegisecurity/providers/x509/populator/DaoX509AuthoritiesPopulator.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -16,29 +16,34 @@
 package org.acegisecurity.providers.x509.populator;
 
 import org.acegisecurity.AuthenticationException;
-import org.acegisecurity.UserDetails;
 import org.acegisecurity.BadCredentialsException;
+import org.acegisecurity.UserDetails;
+
 import org.acegisecurity.providers.dao.AuthenticationDao;
 import org.acegisecurity.providers.x509.X509AuthoritiesPopulator;
 
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.util.Assert;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.apache.oro.text.regex.*;
 
-import java.security.cert.X509Certificate;
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
 
+import org.springframework.util.Assert;
+
+import java.security.cert.X509Certificate;
 
 
 /**
- * Populates the X509 authorities via an {@link org.acegisecurity.providers.dao.AuthenticationDao}.
- *
- * @author Luke Taylor
- * @version $Id$
+ * Populates the X509 authorities via an {@link
+ * org.acegisecurity.providers.dao.AuthenticationDao}.
  */
 public class DaoX509AuthoritiesPopulator implements X509AuthoritiesPopulator,
-    InitializingBean {
+    InitializingBean, MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(DaoX509AuthoritiesPopulator.class);
@@ -46,18 +51,64 @@ public class DaoX509AuthoritiesPopulator implements X509AuthoritiesPopulator,
     //~ Instance fields ========================================================
 
     private AuthenticationDao authenticationDao;
-    private String subjectDNRegex = "CN=(.*?),";
+    protected MessageSourceAccessor messages;
     private Pattern subjectDNPattern;
+    private String subjectDNRegex = "CN=(.*?),";
 
     //~ Methods ================================================================
 
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(authenticationDao, "An authenticationDao must be set");
+        Assert.notNull(this.messages, "A message source must be set");
+
+        Perl5Compiler compiler = new Perl5Compiler();
+
+        try {
+            subjectDNPattern = compiler.compile(subjectDNRegex,
+                    Perl5Compiler.READ_ONLY_MASK
+                    | Perl5Compiler.CASE_INSENSITIVE_MASK);
+        } catch (MalformedPatternException mpe) {
+            throw new IllegalArgumentException("Malformed regular expression: "
+                + subjectDNRegex);
+        }
+    }
+
+    public UserDetails getUserDetails(X509Certificate clientCert)
+        throws AuthenticationException {
+        String subjectDN = clientCert.getSubjectDN().getName();
+        PatternMatcher matcher = new Perl5Matcher();
+
+        if (!matcher.contains(subjectDN, subjectDNPattern)) {
+            throw new BadCredentialsException(messages.getMessage(
+                    "DaoX509AuthoritiesPopulator.noMatching",
+                    new Object[] {subjectDN},
+                    "No matching pattern was found in subjectDN: {0}"));
+        }
+
+        MatchResult match = matcher.getMatch();
+
+        if (match.groups() != 2) { // 2 = 1 + the entire match
+            throw new IllegalArgumentException(
+                "Regular expression must contain a single group ");
+        }
+
+        String userName = match.group(1);
+
+        return this.authenticationDao.loadUserByUsername(userName);
+    }
+
     public void setAuthenticationDao(AuthenticationDao authenticationDao) {
         this.authenticationDao = authenticationDao;
     }
 
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
     /**
      * Sets the regular expression which will by used to extract the user name
      * from the certificate's Subject DN.
+     *
      * <p>
      * It should contain a single group; for example the default expression
      * "CN=(.*?)," matches the common name field. So "CN=Jimi Hendrix, OU=..."
@@ -73,36 +124,4 @@ public class DaoX509AuthoritiesPopulator implements X509AuthoritiesPopulator,
     public void setSubjectDNRegex(String subjectDNRegex) {
         this.subjectDNRegex = subjectDNRegex;
     }
-
-    public UserDetails getUserDetails(X509Certificate clientCert)
-        throws AuthenticationException {
-
-        String subjectDN = clientCert.getSubjectDN().getName();
-        PatternMatcher matcher = new Perl5Matcher();
-
-        if(!matcher.contains(subjectDN , subjectDNPattern)) {
-            throw new BadCredentialsException("No matching pattern was found in subjectDN: " + subjectDN);
-        }
-
-        MatchResult match = matcher.getMatch();
-        if(match.groups() != 2) { // 2 = 1 + the entire match
-            throw new IllegalArgumentException("Regular expression must contain a single group ");
-        }
-        String userName = match.group(1);
-
-        return this.authenticationDao.loadUserByUsername(userName);
-    }
-
-    public void afterPropertiesSet() throws Exception {
-        Assert.notNull(authenticationDao, "An authenticationDao must be set");
-
-        Perl5Compiler compiler = new Perl5Compiler();
-
-        try {
-            subjectDNPattern = compiler.compile(subjectDNRegex,
-                    Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.CASE_INSENSITIVE_MASK);
-        } catch (MalformedPatternException mpe) {
-            throw new IllegalArgumentException("Malformed regular expression: " + subjectDNRegex);
-        }
-    }
 }

+ 26 - 16
core/src/main/java/org/acegisecurity/runas/RunAsImplAuthenticationProvider.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -18,9 +18,15 @@ package org.acegisecurity.runas;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.BadCredentialsException;
+
 import org.acegisecurity.providers.AuthenticationProvider;
 
 import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 
 
@@ -38,28 +44,19 @@ import org.springframework.util.Assert;
  * <P>
  * If the key does not match, a <code>BadCredentialsException</code> is thrown.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class RunAsImplAuthenticationProvider implements InitializingBean,
-    AuthenticationProvider {
+    AuthenticationProvider, MessageSourceAware {
     //~ Instance fields ========================================================
 
+    protected MessageSourceAccessor messages;
     private String key;
 
     //~ Methods ================================================================
 
-    public void setKey(String key) {
-        this.key = key;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
     public void afterPropertiesSet() throws Exception {
-        Assert.notNull(key, "A Key is required and should match that configured for the RunAsManagerImpl");
+        Assert.notNull(key,
+            "A Key is required and should match that configured for the RunAsManagerImpl");
     }
 
     public Authentication authenticate(Authentication authentication)
@@ -69,11 +66,24 @@ public class RunAsImplAuthenticationProvider implements InitializingBean,
         if (token.getKeyHash() == key.hashCode()) {
             return authentication;
         } else {
-            throw new BadCredentialsException(
-                "The presented RunAsUserToken does not contain the expected key");
+            throw new BadCredentialsException(messages.getMessage(
+                    "RunAsImplAuthenticationProvider.incorrectKey",
+                    "The presented RunAsUserToken does not contain the expected key"));
         }
     }
 
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
     public boolean supports(Class authentication) {
         if (RunAsUserToken.class.isAssignableFrom(authentication)) {
             return true;

+ 126 - 113
core/src/main/java/org/acegisecurity/ui/AbstractProcessingFilter.java

@@ -15,33 +15,43 @@
 
 package org.acegisecurity.ui;
 
-import java.io.IOException;
-import java.util.Properties;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
 import org.acegisecurity.Authentication;
 import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.AuthenticationManager;
+
 import org.acegisecurity.context.SecurityContextHolder;
+
 import org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent;
+
 import org.acegisecurity.ui.rememberme.NullRememberMeServices;
 import org.acegisecurity.ui.rememberme.RememberMeServices;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.springframework.beans.factory.InitializingBean;
+
 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.util.Assert;
 
+import java.io.IOException;
+
+import java.util.Properties;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 
 /**
  * Abstract processor of browser-based HTTP-based authentication requests.
@@ -115,20 +125,16 @@ import org.springframework.util.Assert;
  * 
  * <p>
  * If authentication is successful, an {@link
- * org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent} will be
- * published to the application context. No events will be published if
- * authentication was unsuccessful, because this would generally be recorded
- * via an <code>AuthenticationManager</code>-specific application event.
+ * org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent}
+ * will be published to the application context. No events will be published
+ * if authentication was unsuccessful, because this would generally be
+ * recorded via an <code>AuthenticationManager</code>-specific application
+ * event.
  * </p>
- *
- * @author Ben Alex
- * @author colin sampaleanu
- * @author Ray Krueger
- * @version $Id$
  */
 public abstract class AbstractProcessingFilter implements Filter,
-    InitializingBean, ApplicationEventPublisherAware  {
-    //~ Static fields/initializersApplicationContextAware =============================================
+    InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {
+    //~ Static fields/initializers =============================================
 
     public static final String ACEGI_SECURITY_TARGET_URL_KEY = "ACEGI_SECURITY_TARGET_URL";
     public static final String ACEGI_SECURITY_LAST_EXCEPTION_KEY = "ACEGI_SECURITY_LAST_EXCEPTION";
@@ -138,6 +144,7 @@ public abstract class AbstractProcessingFilter implements Filter,
 
     private ApplicationEventPublisher eventPublisher;
     private AuthenticationManager authenticationManager;
+    protected MessageSourceAccessor messages;
     private Properties exceptionMappings = new Properties();
     private RememberMeServices rememberMeServices = new NullRememberMeServices();
 
@@ -173,65 +180,15 @@ public abstract class AbstractProcessingFilter implements Filter,
 
     //~ Methods ================================================================
 
-    public void setAlwaysUseDefaultTargetUrl(boolean alwaysUseDefaultTargetUrl) {
-        this.alwaysUseDefaultTargetUrl = alwaysUseDefaultTargetUrl;
-    }
-
-    public boolean isAlwaysUseDefaultTargetUrl() {
-        return alwaysUseDefaultTargetUrl;
-    }
-
-    public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
-        this.eventPublisher = eventPublisher;
-    }
-
-    public void setContinueChainBeforeSuccessfulAuthentication(
-        boolean continueChainBeforeSuccessfulAuthentication) {
-        this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
-    }
-
-    public boolean isContinueChainBeforeSuccessfulAuthentication() {
-        return continueChainBeforeSuccessfulAuthentication;
-    }
-
-    /**
-     * Specifies the default <code>filterProcessesUrl</code> for the
-     * implementation.
-     *
-     * @return the default <code>filterProcessesUrl</code>
-     */
-    public abstract String getDefaultFilterProcessesUrl();
-
-    public void setDefaultTargetUrl(String defaultTargetUrl) {
-        this.defaultTargetUrl = defaultTargetUrl;
-    }
-
-    public String getDefaultTargetUrl() {
-        return defaultTargetUrl;
-    }
-
-    public void setExceptionMappings(Properties exceptionMappings) {
-        this.exceptionMappings = exceptionMappings;
-    }
-
-    public Properties getExceptionMappings() {
-        return new Properties(exceptionMappings);
-    }
-
-    public void setFilterProcessesUrl(String filterProcessesUrl) {
-        this.filterProcessesUrl = filterProcessesUrl;
-    }
-
-    public String getFilterProcessesUrl() {
-        return filterProcessesUrl;
-    }
-
-    public void setRememberMeServices(RememberMeServices rememberMeServices) {
-        this.rememberMeServices = rememberMeServices;
-    }
-
-    public RememberMeServices getRememberMeServices() {
-        return rememberMeServices;
+    public void afterPropertiesSet() throws Exception {
+        Assert.hasLength(filterProcessesUrl,
+            "filterProcessesUrl must be specified");
+        Assert.hasLength(defaultTargetUrl, "defaultTargetUrl must be specified");
+        Assert.hasLength(authenticationFailureUrl,
+            "authenticationFailureUrl must be specified");
+        Assert.notNull(authenticationManager,
+            "authenticationManager must be specified");
+        Assert.notNull(this.rememberMeServices);
     }
 
     /**
@@ -247,34 +204,6 @@ public abstract class AbstractProcessingFilter implements Filter,
     public abstract Authentication attemptAuthentication(
         HttpServletRequest request) throws AuthenticationException;
 
-    public void setAuthenticationFailureUrl(String authenticationFailureUrl) {
-        this.authenticationFailureUrl = authenticationFailureUrl;
-    }
-
-    public String getAuthenticationFailureUrl() {
-        return authenticationFailureUrl;
-    }
-
-    public void setAuthenticationManager(
-        AuthenticationManager authenticationManager) {
-        this.authenticationManager = authenticationManager;
-    }
-
-    public AuthenticationManager getAuthenticationManager() {
-        return authenticationManager;
-    }
-
-    public void afterPropertiesSet() throws Exception {
-        Assert.hasLength(filterProcessesUrl,
-            "filterProcessesUrl must be specified");
-        Assert.hasLength(defaultTargetUrl, "defaultTargetUrl must be specified");
-        Assert.hasLength(authenticationFailureUrl,
-            "authenticationFailureUrl must be specified");
-        Assert.notNull(authenticationManager,
-            "authenticationManager must be specified");
-        Assert.notNull(this.rememberMeServices);
-    }
-
     /**
      * Does nothing. We use IoC container lifecycle services instead.
      */
@@ -324,6 +253,38 @@ public abstract class AbstractProcessingFilter implements Filter,
         chain.doFilter(request, response);
     }
 
+    public String getAuthenticationFailureUrl() {
+        return authenticationFailureUrl;
+    }
+
+    public AuthenticationManager getAuthenticationManager() {
+        return authenticationManager;
+    }
+
+    /**
+     * Specifies the default <code>filterProcessesUrl</code> for the
+     * implementation.
+     *
+     * @return the default <code>filterProcessesUrl</code>
+     */
+    public abstract String getDefaultFilterProcessesUrl();
+
+    public String getDefaultTargetUrl() {
+        return defaultTargetUrl;
+    }
+
+    public Properties getExceptionMappings() {
+        return new Properties(exceptionMappings);
+    }
+
+    public String getFilterProcessesUrl() {
+        return filterProcessesUrl;
+    }
+
+    public RememberMeServices getRememberMeServices() {
+        return rememberMeServices;
+    }
+
     /**
      * Does nothing. We use IoC container lifecycle services instead.
      *
@@ -333,6 +294,14 @@ public abstract class AbstractProcessingFilter implements Filter,
      */
     public void init(FilterConfig arg0) throws ServletException {}
 
+    public boolean isAlwaysUseDefaultTargetUrl() {
+        return alwaysUseDefaultTargetUrl;
+    }
+
+    public boolean isContinueChainBeforeSuccessfulAuthentication() {
+        return continueChainBeforeSuccessfulAuthentication;
+    }
+
     protected void onPreAuthentication(HttpServletRequest request,
         HttpServletResponse response) throws IOException {}
 
@@ -380,6 +349,49 @@ public abstract class AbstractProcessingFilter implements Filter,
         return uri.endsWith(request.getContextPath() + filterProcessesUrl);
     }
 
+    public void setAlwaysUseDefaultTargetUrl(boolean alwaysUseDefaultTargetUrl) {
+        this.alwaysUseDefaultTargetUrl = alwaysUseDefaultTargetUrl;
+    }
+
+    public void setApplicationEventPublisher(
+        ApplicationEventPublisher eventPublisher) {
+        this.eventPublisher = eventPublisher;
+    }
+
+    public void setAuthenticationFailureUrl(String authenticationFailureUrl) {
+        this.authenticationFailureUrl = authenticationFailureUrl;
+    }
+
+    public void setAuthenticationManager(
+        AuthenticationManager authenticationManager) {
+        this.authenticationManager = authenticationManager;
+    }
+
+    public void setContinueChainBeforeSuccessfulAuthentication(
+        boolean continueChainBeforeSuccessfulAuthentication) {
+        this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
+    }
+
+    public void setDefaultTargetUrl(String defaultTargetUrl) {
+        this.defaultTargetUrl = defaultTargetUrl;
+    }
+
+    public void setExceptionMappings(Properties exceptionMappings) {
+        this.exceptionMappings = exceptionMappings;
+    }
+
+    public void setFilterProcessesUrl(String filterProcessesUrl) {
+        this.filterProcessesUrl = filterProcessesUrl;
+    }
+
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
+    public void setRememberMeServices(RememberMeServices rememberMeServices) {
+        this.rememberMeServices = rememberMeServices;
+    }
+
     protected void successfulAuthentication(HttpServletRequest request,
         HttpServletResponse response, Authentication authResult)
         throws IOException {
@@ -395,7 +407,8 @@ public abstract class AbstractProcessingFilter implements Filter,
                 + authResult + "'");
         }
 
-        String targetUrl = (String) request.getSession().getAttribute(ACEGI_SECURITY_TARGET_URL_KEY);
+        String targetUrl = (String) request.getSession()
+                                           .getAttribute(ACEGI_SECURITY_TARGET_URL_KEY);
         request.getSession().removeAttribute(ACEGI_SECURITY_TARGET_URL_KEY);
 
         if (alwaysUseDefaultTargetUrl == true) {
@@ -444,8 +457,8 @@ public abstract class AbstractProcessingFilter implements Filter,
         }
 
         try {
-            request.getSession().setAttribute(ACEGI_SECURITY_LAST_EXCEPTION_KEY,
-                failed);
+            request.getSession()
+                   .setAttribute(ACEGI_SECURITY_LAST_EXCEPTION_KEY, failed);
         } catch (Exception ignored) {}
 
         onUnsuccessfulAuthentication(request, response);

+ 118 - 84
core/src/main/java/org/acegisecurity/ui/digestauth/DigestProcessingFilter.java

@@ -19,13 +19,17 @@ import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.AuthenticationServiceException;
 import org.acegisecurity.BadCredentialsException;
 import org.acegisecurity.UserDetails;
+
 import org.acegisecurity.context.SecurityContextHolder;
+
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.providers.dao.AuthenticationDao;
 import org.acegisecurity.providers.dao.UserCache;
 import org.acegisecurity.providers.dao.UsernameNotFoundException;
 import org.acegisecurity.providers.dao.cache.NullUserCache;
+
 import org.acegisecurity.ui.WebAuthenticationDetails;
+
 import org.acegisecurity.util.StringSplitUtils;
 
 import org.apache.commons.codec.binary.Base64;
@@ -35,6 +39,10 @@ import org.apache.commons.logging.LogFactory;
 
 import org.springframework.beans.factory.InitializingBean;
 
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
 import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
@@ -72,19 +80,22 @@ import javax.servlet.http.HttpServletResponse;
  * <p>
  * This Digest implementation has been designed to avoid needing to store
  * session state between invocations. All session management information is
- * stored in the "nonce" that is sent to the client by the {@link DigestProcessingFilterEntryPoint}.
+ * stored in the "nonce" that is sent to the client by the {@link
+ * DigestProcessingFilterEntryPoint}.
  * </p>
  * 
  * <P>
- * If authentication is successful, the resulting {@link org.acegisecurity.Authentication Authentication}
- * object will be placed into the <code>SecurityContextHolder</code>.
+ * If authentication is successful, the resulting {@link
+ * org.acegisecurity.Authentication Authentication} object will be placed into
+ * the <code>SecurityContextHolder</code>.
  * </p>
  * 
  * <p>
- * If authentication fails, an
- * {@link org.acegisecurity.intercept.web.AuthenticationEntryPoint AuthenticationEntryPoint}
- * implementation is called. This must always be {@link DigestProcessingFilterEntryPoint},
- * which will prompt the user to authenticate again via Digest authentication.
+ * If authentication fails, an {@link
+ * org.acegisecurity.intercept.web.AuthenticationEntryPoint
+ * AuthenticationEntryPoint} implementation is called. This must always be
+ * {@link DigestProcessingFilterEntryPoint}, which will prompt the user to
+ * authenticate again via Digest authentication.
  * </p>
  * 
  * <P>
@@ -100,11 +111,9 @@ import javax.servlet.http.HttpServletResponse;
  * <code>web.xml</code> to use the {@link
  * org.acegisecurity.util.FilterToBeanProxy}.
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
-public class DigestProcessingFilter implements Filter, InitializingBean {
+public class DigestProcessingFilter implements Filter, InitializingBean,
+    MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(DigestProcessingFilter.class);
@@ -113,32 +122,12 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
 
     private AuthenticationDao authenticationDao;
     private DigestProcessingFilterEntryPoint authenticationEntryPoint;
+    protected MessageSourceAccessor messages;
     private UserCache userCache = new NullUserCache();
     private boolean passwordAlreadyEncoded = false;
 
     //~ Methods ================================================================
 
-    public void setPasswordAlreadyEncoded(boolean passwordAlreadyEncoded) {
-		this.passwordAlreadyEncoded = passwordAlreadyEncoded;
-	}
-
-	public void setAuthenticationDao(AuthenticationDao authenticationDao) {
-        this.authenticationDao = authenticationDao;
-    }
-
-    public AuthenticationDao getAuthenticationDao() {
-        return authenticationDao;
-    }
-
-    public void setAuthenticationEntryPoint(
-        DigestProcessingFilterEntryPoint authenticationEntryPoint) {
-        this.authenticationEntryPoint = authenticationEntryPoint;
-    }
-
-    public DigestProcessingFilterEntryPoint getAuthenticationEntryPoint() {
-        return authenticationEntryPoint;
-    }
-
     public void afterPropertiesSet() throws Exception {
         Assert.notNull(authenticationDao, "An AuthenticationDao is required");
         Assert.notNull(authenticationEntryPoint,
@@ -169,8 +158,7 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
         if ((header != null) && header.startsWith("Digest ")) {
             String section212response = header.substring(7);
 
-            String[] headerEntries = StringUtils
-                .commaDelimitedListToStringArray(section212response);
+            String[] headerEntries = StringUtils.commaDelimitedListToStringArray(section212response);
             Map headerMap = StringSplitUtils.splitEachArrayElementAndCreateMap(headerEntries,
                     "=", "\"");
 
@@ -194,9 +182,10 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
                 }
 
                 fail(request, response,
-                    new BadCredentialsException(
-                        "Missing mandatory digest value; received header '"
-                        + section212response + "'"));
+                    new BadCredentialsException(messages.getMessage(
+                            "DigestProcessingFilter.missingMandatory",
+                            new Object[] {section212response},
+                            "Missing mandatory digest value; received header {0}")));
 
                 return;
             }
@@ -210,9 +199,10 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
                     }
 
                     fail(request, response,
-                        new BadCredentialsException(
-                            "Missing mandatory digest value for 'auth' QOP; received header '"
-                            + section212response + "'"));
+                        new BadCredentialsException(messages.getMessage(
+                                "DigestProcessingFilter.missingAuth",
+                                new Object[] {section212response},
+                                "Missing mandatory digest value; received header {0}")));
 
                     return;
                 }
@@ -221,10 +211,11 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
             // Check realm name equals what we expected
             if (!this.getAuthenticationEntryPoint().getRealmName().equals(realm)) {
                 fail(request, response,
-                    new BadCredentialsException("Response realm name '" + realm
-                        + "' does not match system realm name of '"
-                        + this.getAuthenticationEntryPoint().getRealmName()
-                        + "'"));
+                    new BadCredentialsException(messages.getMessage(
+                            "DigestProcessingFilter.incorrectRealm",
+                            new Object[] {realm, this.getAuthenticationEntryPoint()
+                                                     .getRealmName()},
+                            "Response realm name '{0}' does not match system realm name of '{1}'")));
 
                 return;
             }
@@ -232,9 +223,10 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
             // Check nonce was a Base64 encoded (as sent by DigestProcessingFilterEntryPoint)
             if (!Base64.isArrayByteBase64(nonce.getBytes())) {
                 fail(request, response,
-                    new BadCredentialsException(
-                        "Nonce is not encoded in Base64; received nonce: '"
-                        + nonce + "'"));
+                    new BadCredentialsException(messages.getMessage(
+                            "DigestProcessingFilter.nonceEncoding",
+                            new Object[] {nonce},
+                            "Nonce is not encoded in Base64; received nonce {0}")));
 
                 return;
             }
@@ -249,9 +241,10 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
 
             if (nonceTokens.length != 2) {
                 fail(request, response,
-                    new BadCredentialsException(
-                        "Nonce should have yielded two tokens but was: '"
-                        + nonceAsPlainText + "'"));
+                    new BadCredentialsException(messages.getMessage(
+                            "DigestProcessingFilter.nonceNotTwoTokens",
+                            new Object[] {nonceAsPlainText},
+                            "Nonce should have yielded two tokens but was {0}")));
 
                 return;
             }
@@ -263,9 +256,10 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
                 nonceExpiryTime = new Long(nonceTokens[0]).longValue();
             } catch (NumberFormatException nfe) {
                 fail(request, response,
-                    new BadCredentialsException(
-                        "Nonce token should have yielded a numeric first token, but was: '"
-                        + nonceAsPlainText + "'"));
+                    new BadCredentialsException(messages.getMessage(
+                            "DigestProcessingFilter.nonceNotNumeric",
+                            new Object[] {nonceAsPlainText},
+                            "Nonce token should have yielded a numeric first token, but was {0}")));
 
                 return;
             }
@@ -276,14 +270,17 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
 
             if (!expectedNonceSignature.equals(nonceTokens[1])) {
                 fail(request, response,
-                    new BadCredentialsException("Nonce token compromised: '"
-                        + nonceAsPlainText + "'"));
+                    new BadCredentialsException(messages.getMessage(
+                            "DigestProcessingFilter.nonceCompromised",
+                            new Object[] {nonceAsPlainText},
+                            "Nonce token compromised {0}")));
 
                 return;
             }
 
             // Lookup password for presented username
             // NB: DAO-provided password MUST be clear text - not encoded/salted
+            // (unless this instance's passwordAlreadyEncoded property is 'false')
             boolean loadedFromDao = false;
             UserDetails user = userCache.getUserFromCache(username);
 
@@ -294,8 +291,10 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
                     user = authenticationDao.loadUserByUsername(username);
                 } catch (UsernameNotFoundException notFound) {
                     fail(request, response,
-                        new BadCredentialsException("Username '" + username
-                            + "' not known"));
+                        new BadCredentialsException(messages.getMessage(
+                                "DigestProcessingFilter.usernameNotFound",
+                                new Object[] {username},
+                                "Username {0} not found")));
 
                     return;
                 }
@@ -312,8 +311,8 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
             String serverDigestMd5;
 
             // Don't catch IllegalArgumentException (already checked validity)
-            serverDigestMd5 = generateDigest(passwordAlreadyEncoded, username, realm,
-                    user.getPassword(),
+            serverDigestMd5 = generateDigest(passwordAlreadyEncoded, username,
+                    realm, user.getPassword(),
                     ((HttpServletRequest) request).getMethod(), uri, qop,
                     nonce, nc, cnonce);
 
@@ -329,15 +328,17 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
                 } catch (UsernameNotFoundException notFound) {
                     // Would very rarely happen, as user existed earlier
                     fail(request, response,
-                        new BadCredentialsException("Username '" + username
-                            + "' not known"));
+                        new BadCredentialsException(messages.getMessage(
+                                "DigestProcessingFilter.usernameNotFound",
+                                new Object[] {username},
+                                "Username {0} not found")));
                 }
 
                 userCache.putUserInCache(user);
 
                 // Don't catch IllegalArgumentException (already checked validity)
-                serverDigestMd5 = generateDigest(passwordAlreadyEncoded, username, realm,
-                        user.getPassword(),
+                serverDigestMd5 = generateDigest(passwordAlreadyEncoded,
+                        username, realm, user.getPassword(),
                         ((HttpServletRequest) request).getMethod(), uri, qop,
                         nonce, nc, cnonce);
             }
@@ -351,7 +352,9 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
                 }
 
                 fail(request, response,
-                    new BadCredentialsException("Incorrect response"));
+                    new BadCredentialsException(messages.getMessage(
+                            "DigestProcessingFilter.incorrectResponse",
+                            "Incorrect response")));
 
                 return;
             }
@@ -362,7 +365,9 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
             // but the request was otherwise appearing to be valid
             if (nonceExpiryTime < System.currentTimeMillis()) {
                 fail(request, response,
-                    new NonceExpiredException("Nonce has expired/timed out"));
+                    new NonceExpiredException(messages.getMessage(
+                            "DigestProcessingFilter.nonceExpired",
+                            "Nonce has expired/timed out")));
 
                 return;
             }
@@ -382,18 +387,32 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
         chain.doFilter(request, response);
     }
 
-    public static String encodePasswordInA1Format(String username, String realm, String password) {
+    public static String encodePasswordInA1Format(String username,
+        String realm, String password) {
         String a1 = username + ":" + realm + ":" + password;
         String a1Md5 = new String(DigestUtils.md5Hex(a1));
-    	return a1Md5;
+
+        return a1Md5;
     }
-    
+
+    private void fail(ServletRequest request, ServletResponse response,
+        AuthenticationException failed) throws IOException, ServletException {
+        SecurityContextHolder.getContext().setAuthentication(null);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(failed);
+        }
+
+        authenticationEntryPoint.commence(request, response, failed);
+    }
+
     /**
      * Computes the <code>response</code> portion of a Digest authentication
      * header. Both the server and user agent should compute the
      * <code>response</code> independently. Provided as a static method to
      * simplify the coding of user agents.
      *
+     * @param passwordAlreadyEncoded DOCUMENT ME!
      * @param username DOCUMENT ME!
      * @param realm DOCUMENT ME!
      * @param password DOCUMENT ME!
@@ -408,19 +427,20 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
      *
      * @throws IllegalArgumentException DOCUMENT ME!
      */
-    public static String generateDigest(boolean passwordAlreadyEncoded, String username, String realm,
-        String password, String httpMethod, String uri, String qop,
-        String nonce, String nc, String cnonce) throws IllegalArgumentException {
+    public static String generateDigest(boolean passwordAlreadyEncoded,
+        String username, String realm, String password, String httpMethod,
+        String uri, String qop, String nonce, String nc, String cnonce)
+        throws IllegalArgumentException {
         String a1Md5 = null;
         String a2 = httpMethod + ":" + uri;
         String a2Md5 = new String(DigestUtils.md5Hex(a2));
-        
+
         if (passwordAlreadyEncoded) {
-        	a1Md5 = password;
+            a1Md5 = password;
         } else {
-        	a1Md5 = encodePasswordInA1Format(username, realm, password);
+            a1Md5 = encodePasswordInA1Format(username, realm, password);
         }
-        
+
         String digest;
 
         if (qop == null) {
@@ -440,8 +460,12 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
         return digestMd5;
     }
 
-    public void setUserCache(UserCache userCache) {
-        this.userCache = userCache;
+    public AuthenticationDao getAuthenticationDao() {
+        return authenticationDao;
+    }
+
+    public DigestProcessingFilterEntryPoint getAuthenticationEntryPoint() {
+        return authenticationEntryPoint;
     }
 
     public UserCache getUserCache() {
@@ -450,14 +474,24 @@ public class DigestProcessingFilter implements Filter, InitializingBean {
 
     public void init(FilterConfig ignored) throws ServletException {}
 
-    private void fail(ServletRequest request, ServletResponse response,
-        AuthenticationException failed) throws IOException, ServletException {
-        SecurityContextHolder.getContext().setAuthentication(null);
+    public void setAuthenticationDao(AuthenticationDao authenticationDao) {
+        this.authenticationDao = authenticationDao;
+    }
 
-        if (logger.isDebugEnabled()) {
-            logger.debug(failed);
-        }
+    public void setAuthenticationEntryPoint(
+        DigestProcessingFilterEntryPoint authenticationEntryPoint) {
+        this.authenticationEntryPoint = authenticationEntryPoint;
+    }
 
-        authenticationEntryPoint.commence(request, response, failed);
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
+    }
+
+    public void setPasswordAlreadyEncoded(boolean passwordAlreadyEncoded) {
+        this.passwordAlreadyEncoded = passwordAlreadyEncoded;
+    }
+
+    public void setUserCache(UserCache userCache) {
+        this.userCache = userCache;
     }
 }

+ 405 - 316
core/src/main/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilter.java

@@ -22,12 +22,17 @@ import org.acegisecurity.AuthenticationException;
 import org.acegisecurity.CredentialsExpiredException;
 import org.acegisecurity.DisabledException;
 import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.LockedException;
 import org.acegisecurity.UserDetails;
+
 import org.acegisecurity.context.SecurityContextHolder;
+
 import org.acegisecurity.event.authentication.AuthenticationSwitchUserEvent;
+
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.providers.dao.AuthenticationDao;
 import org.acegisecurity.providers.dao.UsernameNotFoundException;
+
 import org.acegisecurity.ui.WebAuthenticationDetails;
 
 import org.apache.commons.logging.Log;
@@ -36,8 +41,11 @@ import org.apache.commons.logging.LogFactory;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.InitializingBean;
 
-import org.springframework.context.ApplicationEventPublisherAware;
 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.util.Assert;
 
@@ -80,8 +88,8 @@ import javax.servlet.http.HttpServletResponse;
  * <p>
  * On successful switch, the user's  <code>SecurityContextHolder</code> will be
  * updated to reflect the specified user and will also contain an additinal
- * {@link org.acegisecurity.ui.switchuser.SwitchUserGrantedAuthority }
- * which contains the original user.
+ * {@link org.acegisecurity.ui.switchuser.SwitchUserGrantedAuthority } which
+ * contains the original user.
  * </p>
  * 
  * <p>
@@ -104,13 +112,10 @@ import javax.servlet.http.HttpServletResponse;
  * </pre>
  * </p>
  *
- * @author Mark St.Godard
- * @version $Id$
- *
  * @see org.acegisecurity.ui.switchuser.SwitchUserGrantedAuthority
  */
 public class SwitchUserProcessingFilter implements Filter, InitializingBean,
-        ApplicationEventPublisherAware {
+    ApplicationEventPublisherAware, MessageSourceAware {
     //~ Static fields/initializers =============================================
 
     private static final Log logger = LogFactory.getLog(SwitchUserProcessingFilter.class);
@@ -127,105 +132,21 @@ public class SwitchUserProcessingFilter implements Filter, InitializingBean,
     // ~ Instance fields
     // ========================================================
     private AuthenticationDao authenticationDao;
+    protected MessageSourceAccessor messages;
     private String exitUserUrl = "/j_acegi_exit_user";
     private String switchUserUrl = "/j_acegi_switch_user";
     private String targetUrl;
 
     //~ Methods ================================================================
 
-    public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher)
-        throws BeansException {
-        this.eventPublisher = eventPublisher;
-    }
-
-    /**
-     * Sets the authentication data access object.
-     *
-     * @param authenticationDao The authentication dao
-     */
-    public void setAuthenticationDao(AuthenticationDao authenticationDao) {
-        this.authenticationDao = authenticationDao;
-    }
-
-    /**
-     * Set the URL to respond to exit user processing.
-     *
-     * @param exitUserUrl The exit user URL.
-     */
-    public void setExitUserUrl(String exitUserUrl) {
-        this.exitUserUrl = exitUserUrl;
-    }
-
-    /**
-     * Set the URL to respond to switch user processing.
-     *
-     * @param switchUserUrl The switch user URL.
-     */
-    public void setSwitchUserUrl(String switchUserUrl) {
-        this.switchUserUrl = switchUserUrl;
-    }
-
-    /**
-     * Sets the URL to go to after a successful switch / exit user request.
-     *
-     * @param targetUrl The target url.
-     */
-    public void setTargetUrl(String targetUrl) {
-        this.targetUrl = targetUrl;
-    }
-
     public void afterPropertiesSet() throws Exception {
         Assert.hasLength(switchUserUrl, "switchUserUrl must be specified");
         Assert.hasLength(exitUserUrl, "exitUserUrl must be specified");
         Assert.hasLength(targetUrl, "targetUrl must be specified");
         Assert.notNull(authenticationDao, "authenticationDao must be specified");
+        Assert.notNull(messages, "A message source must be set");
     }
 
-    public void destroy() {}
-
-    /**
-     * @see javax.servlet.Filter#doFilter
-     */
-    public void doFilter(ServletRequest request, ServletResponse response,
-        FilterChain chain) throws IOException, ServletException {
-        Assert.isInstanceOf(HttpServletRequest.class, request);
-        Assert.isInstanceOf(HttpServletResponse.class, response);
-
-        HttpServletRequest httpRequest = (HttpServletRequest) request;
-        HttpServletResponse httpResponse = (HttpServletResponse) response;
-
-        // check for switch or exit request
-        if (requiresSwitchUser(httpRequest)) {
-            // if set, attempt switch and store original 
-            Authentication targetUser = attemptSwitchUser(httpRequest);
-
-            // update the current context to the new target user
-            SecurityContextHolder.getContext().setAuthentication(targetUser);
-
-            // redirect to target url
-            httpResponse.sendRedirect(httpResponse.encodeRedirectURL(httpRequest
-                    .getContextPath() + targetUrl));
-
-            return;
-        } else if (requiresExitUser(httpRequest)) {
-            // get the original authentication object (if exists)
-            Authentication originalUser = attemptExitUser(httpRequest);
-
-            // update the current context back to the original user
-            SecurityContextHolder.getContext().setAuthentication(originalUser);
-
-            // redirect to target url
-            httpResponse.sendRedirect(httpResponse.encodeRedirectURL(httpRequest
-                    .getContextPath() + targetUrl));
-
-            return;
-        }
-
-        chain.doFilter(request, response);
-    }
-
-    public void init(FilterConfig ignored) throws ServletException {}
-
     /**
      * Attempt to exit from an already switched user.
      *
@@ -244,229 +165,397 @@ public class SwitchUserProcessingFilter implements Filter, InitializingBean,
                                                       .getAuthentication();
 
         if (null == current) {
-            throw new AuthenticationCredentialsNotFoundException(
-                "No current user associated with this request!");
-        }
-
-        // check to see if the current user did actual switch to another user
-        // if so, get the original source user so we can switch back
-        Authentication original = getSourceAuthentication(current);
-
-        if (original == null) {
-            logger.error("Could not find original user Authentication object!");
-            throw new AuthenticationCredentialsNotFoundException(
-                "Could not find original Authentication object!");
-        }
-
-        // get the source user details
-        UserDetails originalUser = null;
-        Object obj = original.getPrincipal();
-
-        if ((obj != null) && obj instanceof UserDetails) {
-            originalUser = (UserDetails) obj;
-        }
-
-        // publish event
-        if (this.eventPublisher != null) {
-            eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(current,
-                    originalUser));
-        }
-
-        return original;
-    }
-
-    /**
-     * Attempt to switch to another user. If the user does not exist or is not
-     * active, return null.
-     *
-     * @param request The http request
-     *
-     * @return The new <code>Authentication</code> request if successfully
-     *         switched to another user, <code>null</code> otherwise.
-     *
-     * @throws AuthenticationException
-     * @throws UsernameNotFoundException If the target user is not found.
-     * @throws DisabledException If the target user is disabled.
-     * @throws AccountExpiredException If the target user account is expired.
-     * @throws CredentialsExpiredException If the target user credentials are
-     *         expired.
-     */
-    protected Authentication attemptSwitchUser(HttpServletRequest request)
-        throws AuthenticationException {
-        UsernamePasswordAuthenticationToken targetUserRequest = null;
-
-        String username = request.getParameter(ACEGI_SECURITY_SWITCH_USERNAME_KEY);
-
-        if (username == null) {
-            username = "";
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Attempt to switch to user [" + username + "]");
-        }
-
-        // load the user by name
-        UserDetails targetUser = this.authenticationDao.loadUserByUsername(username);
-
-        // user not found
-        if (targetUser == null) {
-            throw new UsernameNotFoundException("User [" + username
-                + "] cannot be found!");
-        }
-
-        // user is disabled
-        if (!targetUser.isEnabled()) {
-            throw new DisabledException("User is disabled");
-        }
-
-        // account is expired
-        if (!targetUser.isAccountNonExpired()) {
-            throw new AccountExpiredException("User account has expired");
-        }
-
-        // credentials expired
-        if (!targetUser.isCredentialsNonExpired()) {
-            throw new CredentialsExpiredException("User credentials expired");
-        }
-
-        // ok, create the switch user token
-        targetUserRequest = createSwitchUserToken(request, username, targetUser);
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Switch User Token [" + targetUserRequest + "]");
-        }
-
-        // publish event
-        if (this.eventPublisher != null) {
-            eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
-                    SecurityContextHolder.getContext().getAuthentication(),
-                    targetUser));
-        }
-
-        return targetUserRequest;
-    }
-
-    /**
-     * Checks the request URI for the presence of <tt>exitUserUrl</tt>.
-     *
-     * @param request The http servlet request
-     *
-     * @return <code>true</code> if the request requires a exit user,
-     *         <code>false</code> otherwise.
-     *
-     * @see SwitchUserProcessingFilter#exitUserUrl
-     */
-    protected boolean requiresExitUser(HttpServletRequest request) {
-        String uri = stripUri(request);
-
-        return uri.endsWith(request.getContextPath() + exitUserUrl);
-    }
-
-    /**
-     * Checks the request URI for the presence of <tt>switchUserUrl</tt>.
-     *
-     * @param request The http servlet request
-     *
-     * @return <code>true</code> if the request requires a switch,
-     *         <code>false</code> otherwise.
-     *
-     * @see SwitchUserProcessingFilter#switchUserUrl
-     */
-    protected boolean requiresSwitchUser(HttpServletRequest request) {
-        String uri = stripUri(request);
-
-        return uri.endsWith(request.getContextPath() + switchUserUrl);
-    }
-
-    /**
-     * Strips any content after the ';' in the request URI
-     *
-     * @param request The http request
-     *
-     * @return The stripped uri
-     */
-    private static String stripUri(HttpServletRequest request) {
-        String uri = request.getRequestURI();
-        int idx = uri.indexOf(';');
-
-        if (idx > 0) {
-            uri = uri.substring(0, idx);
-        }
-
-        return uri;
-    }
-
-    /**
-     * Find the original <code>Authentication</code> object from the current
-     * user's granted authorities. A successfully switched user should have a
-     * <code>SwitchUserGrantedAuthority</code> that contains the original
-     * source user <code>Authentication</code> object.
-     *
-     * @param current The current <code>Authentication</code> object
-     *
-     * @return The source user <code>Authentication</code> object or
-     *         <code>null</code> otherwise.
-     */
-    private Authentication getSourceAuthentication(Authentication current) {
-        Authentication original = null;
-
-        // iterate over granted authorities and find the 'switch user' authority
-        GrantedAuthority[] authorities = current.getAuthorities();
-
-        for (int i = 0; i < authorities.length; i++) {
-            // check for switch user type of authority
-            if (authorities[i] instanceof SwitchUserGrantedAuthority) {
-                original = ((SwitchUserGrantedAuthority) authorities[i])
-                    .getSource();
-                logger.debug("Found original switch user granted authority ["
-                    + original + "]");
+            throw new AuthenticationCredentialsNotFoundException(messages
+                    .getMessage("SwitchUserProcessingFilter.noCurrentUser",
+                        "No current user associated with this request"));
             }
-        }
-
-        return original;
-    }
-
-    /**
-     * Create a switch user token that contains an additional
-     * <tt>GrantedAuthority</tt> that contains the original
-     * <code>Authentication</code> object.
-     *
-     * @param request The http servlet request.
-     * @param username The username of target user
-     * @param targetUser The target user
-     *
-     * @return The authentication token
-     *
-     * @see SwitchUserGrantedAuthority
-     */
-    private UsernamePasswordAuthenticationToken createSwitchUserToken(
-        HttpServletRequest request, String username, UserDetails targetUser) {
-        UsernamePasswordAuthenticationToken targetUserRequest;
-
-        // grant an additional authority that contains the original Authentication object
-        // which will be used to 'exit' from the current switched user.
-        Authentication currentAuth = SecurityContextHolder.getContext()
-                                                          .getAuthentication();
-        GrantedAuthority switchAuthority = new SwitchUserGrantedAuthority(ROLE_PREVIOUS_ADMINISTRATOR,
-                currentAuth);
 
-        // get the original authorities
-        List orig = Arrays.asList(targetUser.getAuthorities());
-
-        // add the new switch user authority
-        List newAuths = new ArrayList(orig);
-        newAuths.add(switchAuthority);
-
-        GrantedAuthority[] authorities = {};
-        authorities = (GrantedAuthority[]) newAuths.toArray(authorities);
-
-        // create the new authentication token
-        targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser,
-                targetUser.getPassword(), authorities);
-
-        // set details
-        targetUserRequest.setDetails(new WebAuthenticationDetails(request));
+            // check to see if the current user did actual switch to another user
+            // if so, get the original source user so we can switch back
+            Authentication original = getSourceAuthentication(current);
+
+            if (original == null) {
+                logger.error(
+                    "Could not find original user Authentication object!");
+                throw new AuthenticationCredentialsNotFoundException(messages
+                        .getMessage(
+                            "SwitchUserProcessingFilter.noOriginalAuthentication",
+                            "Could not find original Authentication object"));
+                }
+
+                // get the source user details
+                UserDetails originalUser = null;
+                Object obj = original.getPrincipal();
+
+                if ((obj != null) && obj instanceof UserDetails) {
+                    originalUser = (UserDetails) obj;
+                }
+
+                // publish event
+                if (this.eventPublisher != null) {
+                    eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
+                            current, originalUser));
+                }
+
+                return original;
+            }
 
-        return targetUserRequest;
-    }
-}
+            /**
+             * Attempt to switch to another user. If the user does not exist or
+             * is not active, return null.
+             *
+             * @param request The http request
+             *
+             * @return The new <code>Authentication</code> request if
+             *         successfully switched to another user,
+             *         <code>null</code> otherwise.
+             *
+             * @throws AuthenticationException
+             * @throws UsernameNotFoundException If the target user is not
+             *         found.
+             * @throws LockedException DOCUMENT ME!
+             * @throws DisabledException If the target user is disabled.
+             * @throws AccountExpiredException If the target user account is
+             *         expired.
+             * @throws CredentialsExpiredException If the target user
+             *         credentials are expired.
+             */
+            protected Authentication attemptSwitchUser(
+                HttpServletRequest request) throws AuthenticationException {
+                UsernamePasswordAuthenticationToken targetUserRequest = null;
+
+                String username = request.getParameter(ACEGI_SECURITY_SWITCH_USERNAME_KEY);
+
+                if (username == null) {
+                    username = "";
+                }
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Attempt to switch to user [" + username + "]");
+                }
+
+                // load the user by name
+                UserDetails targetUser = this.authenticationDao
+                        .loadUserByUsername(username);
+
+                    // user not found
+                    if (targetUser == null) {
+                        throw new UsernameNotFoundException(messages.getMessage(
+                                "SwitchUserProcessingFilter.usernameNotFound",
+                                new Object[] {username},
+                                "Username {0} not found"));
+                    }
+
+                    // account is expired
+                    if (!targetUser.isAccountNonLocked()) {
+                        throw new LockedException(messages.getMessage(
+                                "SwitchUserProcessingFilter.locked",
+                                "User account is locked"));
+                    }
+
+                    // user is disabled
+                    if (!targetUser.isEnabled()) {
+                        throw new DisabledException(messages.getMessage(
+                                "SwitchUserProcessingFilter.disabled",
+                                "User is disabled"));
+                    }
+
+                    // account is expired
+                    if (!targetUser.isAccountNonExpired()) {
+                        throw new AccountExpiredException(messages.getMessage(
+                                "SwitchUserProcessingFilter.expired",
+                                "User account has expired"));
+                    }
+
+                    // credentials expired
+                    if (!targetUser.isCredentialsNonExpired()) {
+                        throw new CredentialsExpiredException(messages
+                                .getMessage(
+                                    "SwitchUserProcessingFilter.credentialsExpired",
+                                    "User credentials have expired"));
+                        }
+
+                        // ok, create the switch user token
+                        targetUserRequest = createSwitchUserToken(request,
+                                username, targetUser);
+
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Switch User Token ["
+                                + targetUserRequest + "]");
+                        }
+
+                        // publish event
+                        if (this.eventPublisher != null) {
+                            eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
+                                    SecurityContextHolder.getContext()
+                                                         .getAuthentication(),
+                                    targetUser));
+                        }
+
+                        return targetUserRequest;
+                    }
+
+                    /**
+                     * Create a switch user token that contains an additional
+                     * <tt>GrantedAuthority</tt> that contains the original
+                     * <code>Authentication</code> object.
+                     *
+                     * @param request The http servlet request.
+                     * @param username The username of target user
+                     * @param targetUser The target user
+                     *
+                     * @return The authentication token
+                     *
+                     * @see SwitchUserGrantedAuthority
+                     */
+                    private UsernamePasswordAuthenticationToken createSwitchUserToken(
+                        HttpServletRequest request, String username,
+                        UserDetails targetUser) {
+                        UsernamePasswordAuthenticationToken targetUserRequest;
+
+                        // grant an additional authority that contains the original Authentication object
+                        // which will be used to 'exit' from the current switched user.
+                        Authentication currentAuth = SecurityContextHolder.getContext()
+                                                                          .getAuthentication();
+                        GrantedAuthority switchAuthority = new SwitchUserGrantedAuthority(ROLE_PREVIOUS_ADMINISTRATOR,
+                                currentAuth);
+
+                        // get the original authorities
+                        List orig = Arrays.asList(targetUser.getAuthorities());
+
+                        // add the new switch user authority
+                        List newAuths = new ArrayList(orig);
+                        newAuths.add(switchAuthority);
+
+                        GrantedAuthority[] authorities = {};
+                        authorities = (GrantedAuthority[]) newAuths.toArray(authorities);
+
+                        // create the new authentication token
+                        targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser,
+                                targetUser.getPassword(), authorities);
+
+                        // set details
+                        targetUserRequest.setDetails(new WebAuthenticationDetails(
+                                request));
+
+                        return targetUserRequest;
+                    }
+
+                    public void destroy() {}
+
+                    /**
+                     * @see javax.servlet.Filter#doFilter
+                     */
+                    public void doFilter(ServletRequest request,
+                        ServletResponse response, FilterChain chain)
+                        throws IOException, ServletException {
+                        Assert.isInstanceOf(HttpServletRequest.class, request);
+                        Assert.isInstanceOf(HttpServletResponse.class, response);
+
+                        HttpServletRequest httpRequest = (HttpServletRequest) request;
+                        HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+                        // check for switch or exit request
+                        if (requiresSwitchUser(httpRequest)) {
+                            // if set, attempt switch and store original 
+                            Authentication targetUser = attemptSwitchUser(httpRequest);
+
+                            // update the current context to the new target user
+                            SecurityContextHolder.getContext()
+                                                 .setAuthentication(targetUser);
+
+                            // redirect to target url
+                            httpResponse.sendRedirect(httpResponse
+                                    .encodeRedirectURL(httpRequest
+                                            .getContextPath() + targetUrl));
+
+                                    return;
+                                } else if (requiresExitUser(httpRequest)) {
+                                    // get the original authentication object (if exists)
+                                    Authentication originalUser = attemptExitUser(httpRequest);
+
+                                    // update the current context back to the original user
+                                    SecurityContextHolder.getContext()
+                                                         .setAuthentication(originalUser);
+
+                                    // redirect to target url
+                                    httpResponse.sendRedirect(httpResponse
+                                            .encodeRedirectURL(httpRequest
+                                                    .getContextPath()
+                                                    + targetUrl));
+
+                                            return;
+                                        }
+
+                                        chain.doFilter(request, response);
+                                    }
+
+                                    /**
+                                     * Find the original
+                                     * <code>Authentication</code> object from
+                                     * the current user's granted authorities.
+                                     * A successfully switched user should
+                                     * have a
+                                     * <code>SwitchUserGrantedAuthority</code>
+                                     * that contains the original source user
+                                     * <code>Authentication</code> object.
+                                     *
+                                     * @param current The current
+                                     *        <code>Authentication</code>
+                                     *        object
+                                     *
+                                     * @return The source user
+                                     *         <code>Authentication</code>
+                                     *         object or <code>null</code>
+                                     *         otherwise.
+                                     */
+                                    private Authentication getSourceAuthentication(
+                                        Authentication current) {
+                                        Authentication original = null;
+
+                                        // iterate over granted authorities and find the 'switch user' authority
+                                        GrantedAuthority[] authorities = current
+                                            .getAuthorities();
+
+                                        for (int i = 0; i < authorities.length;
+                                            i++) {
+                                            // check for switch user type of authority
+                                            if (authorities[i] instanceof SwitchUserGrantedAuthority) {
+                                                original = ((SwitchUserGrantedAuthority) authorities[i])
+                                                    .getSource();
+                                                logger.debug(
+                                                    "Found original switch user granted authority ["
+                                                    + original + "]");
+                                            }
+                                        }
+
+                                        return original;
+                                    }
+
+                                    public void init(FilterConfig ignored)
+                                        throws ServletException {}
+
+                                    /**
+                                     * Checks the request URI for the presence
+                                     * of <tt>exitUserUrl</tt>.
+                                     *
+                                     * @param request The http servlet request
+                                     *
+                                     * @return <code>true</code> if the request
+                                     *         requires a exit user,
+                                     *         <code>false</code> otherwise.
+                                     *
+                                     * @see SwitchUserProcessingFilter#exitUserUrl
+                                     */
+                                    protected boolean requiresExitUser(
+                                        HttpServletRequest request) {
+                                        String uri = stripUri(request);
+
+                                        return uri.endsWith(request
+                                                .getContextPath() + exitUserUrl);
+                                        }
+
+                                        /**
+                                         * Checks the request URI for the
+                                         * presence of <tt>switchUserUrl</tt>.
+                                         *
+                                         * @param request The http servlet
+                                         *        request
+                                         *
+                                         * @return <code>true</code> if the
+                                         *         request requires a switch,
+                                         *         <code>false</code>
+                                         *         otherwise.
+                                         *
+                                         * @see SwitchUserProcessingFilter#switchUserUrl
+                                         */
+                                        protected boolean requiresSwitchUser(
+                                            HttpServletRequest request) {
+                                            String uri = stripUri(request);
+
+                                            return uri.endsWith(request
+                                                    .getContextPath()
+                                                    + switchUserUrl);
+                                            }
+
+                                            public void setApplicationEventPublisher(
+                                                ApplicationEventPublisher eventPublisher)
+                                                throws BeansException {
+                                                this.eventPublisher = eventPublisher;
+                                            }
+
+                                            /**
+                                             * Sets the authentication data
+                                             * access object.
+                                             *
+                                             * @param authenticationDao The
+                                             *        authentication dao
+                                             */
+                                            public void setAuthenticationDao(
+                                                AuthenticationDao authenticationDao) {
+                                                this.authenticationDao = authenticationDao;
+                                            }
+
+                                            /**
+                                             * Set the URL to respond to exit
+                                             * user processing.
+                                             *
+                                             * @param exitUserUrl The exit user
+                                             *        URL.
+                                             */
+                                            public void setExitUserUrl(
+                                                String exitUserUrl) {
+                                                this.exitUserUrl = exitUserUrl;
+                                            }
+
+                                            public void setMessageSource(
+                                                MessageSource messageSource) {
+                                                this.messages = new MessageSourceAccessor(messageSource);
+                                            }
+
+                                            /**
+                                             * Set the URL to respond to switch
+                                             * user processing.
+                                             *
+                                             * @param switchUserUrl The switch
+                                             *        user URL.
+                                             */
+                                            public void setSwitchUserUrl(
+                                                String switchUserUrl) {
+                                                this.switchUserUrl = switchUserUrl;
+                                            }
+
+                                            /**
+                                             * Sets the URL to go to after a
+                                             * successful switch / exit user
+                                             * request.
+                                             *
+                                             * @param targetUrl The target url.
+                                             */
+                                            public void setTargetUrl(
+                                                String targetUrl) {
+                                                this.targetUrl = targetUrl;
+                                            }
+
+                                            /**
+                                             * Strips any content after the ';'
+                                             * in the request URI
+                                             *
+                                             * @param request The http request
+                                             *
+                                             * @return The stripped uri
+                                             */
+                                            private static String stripUri(
+                                                HttpServletRequest request) {
+                                                String uri = request
+                                                        .getRequestURI();
+                                                    int idx = uri.indexOf(';');
+
+                                                    if (idx > 0) {
+                                                        uri = uri.substring(0,
+                                                                idx);
+                                                    }
+
+                                                    return uri;
+                                                }
+                                            }

+ 30 - 21
core/src/main/java/org/acegisecurity/vote/AbstractAccessDecisionManager.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -20,6 +20,12 @@ import org.acegisecurity.ConfigAttribute;
 
 import org.springframework.beans.factory.InitializingBean;
 
+import org.springframework.context.MessageSource;
+import org.springframework.context.MessageSourceAware;
+import org.springframework.context.support.MessageSourceAccessor;
+
+import org.springframework.util.Assert;
+
 import java.util.Iterator;
 import java.util.List;
 
@@ -32,28 +38,42 @@ import java.util.List;
  * AccessDecisionVoter}s and the access control behaviour if all  voters
  * abstain from voting (defaults to deny access).
  * </p>
- *
- * @author Ben Alex
- * @version $Id$
  */
 public abstract class AbstractAccessDecisionManager
-    implements AccessDecisionManager, InitializingBean {
+    implements AccessDecisionManager, InitializingBean, MessageSourceAware {
     //~ Instance fields ========================================================
 
     private List decisionVoters;
+    protected MessageSourceAccessor messages;
     private boolean allowIfAllAbstainDecisions = false;
 
     //~ Methods ================================================================
 
-    public void setAllowIfAllAbstainDecisions(
-        boolean allowIfAllAbstainDecisions) {
-        this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
+    public void afterPropertiesSet() throws Exception {
+        checkIfValidList(this.decisionVoters);
+        Assert.notNull(this.messages, "A message source must be set");
+    }
+
+    private void checkIfValidList(List listToCheck) {
+        if ((listToCheck == null) || (listToCheck.size() == 0)) {
+            throw new IllegalArgumentException(
+                "A list of AccessDecisionVoters is required");
+        }
+    }
+
+    public List getDecisionVoters() {
+        return this.decisionVoters;
     }
 
     public boolean isAllowIfAllAbstainDecisions() {
         return allowIfAllAbstainDecisions;
     }
 
+    public void setAllowIfAllAbstainDecisions(
+        boolean allowIfAllAbstainDecisions) {
+        this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
+    }
+
     public void setDecisionVoters(List newList) {
         checkIfValidList(newList);
 
@@ -76,12 +96,8 @@ public abstract class AbstractAccessDecisionManager
         this.decisionVoters = newList;
     }
 
-    public List getDecisionVoters() {
-        return this.decisionVoters;
-    }
-
-    public void afterPropertiesSet() throws Exception {
-        checkIfValidList(this.decisionVoters);
+    public void setMessageSource(MessageSource messageSource) {
+        this.messages = new MessageSourceAccessor(messageSource);
     }
 
     public boolean supports(ConfigAttribute attribute) {
@@ -124,11 +140,4 @@ public abstract class AbstractAccessDecisionManager
 
         return true;
     }
-
-    private void checkIfValidList(List listToCheck) {
-        if ((listToCheck == null) || (listToCheck.size() == 0)) {
-            throw new IllegalArgumentException(
-                "A list of AccessDecisionVoters is required");
-        }
-    }
 }

+ 7 - 6
core/src/main/java/org/acegisecurity/vote/AffirmativeBased.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -29,9 +29,6 @@ import java.util.Iterator;
  * Simple concrete implementation of  {@link
  * org.acegisecurity.AccessDecisionManager} that grants access if any
  * <code>AccessDecisionVoter</code> returns an affirmative response.
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class AffirmativeBased extends AbstractAccessDecisionManager {
     //~ Static fields/initializers =============================================
@@ -83,14 +80,18 @@ public class AffirmativeBased extends AbstractAccessDecisionManager {
         }
 
         if (deny > 0) {
-            throw new AccessDeniedException("Access is denied.");
+            throw new AccessDeniedException(messages.getMessage(
+                    "AbstractAccessDecisionManager.accessDenied",
+                    "Access is denied"));
         }
 
         // To get this far, every AccessDecisionVoter abstained
         if (this.isAllowIfAllAbstainDecisions()) {
             return;
         } else {
-            throw new AccessDeniedException("Access is denied.");
+            throw new AccessDeniedException(messages.getMessage(
+                    "AbstractAccessDecisionManager.accessDenied",
+                    "Access is denied"));
         }
     }
 }

+ 19 - 16
core/src/main/java/org/acegisecurity/vote/ConsensusBased.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -29,9 +29,6 @@ import java.util.Iterator;
  * Simple concrete implementation of  {@link
  * org.acegisecurity.AccessDecisionManager} that uses a  consensus-based
  * approach.
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class ConsensusBased extends AbstractAccessDecisionManager {
     //~ Static fields/initializers =============================================
@@ -44,15 +41,6 @@ public class ConsensusBased extends AbstractAccessDecisionManager {
 
     //~ Methods ================================================================
 
-    public void setAllowIfEqualGrantedDeniedDecisions(
-        boolean allowIfEqualGrantedDeniedDecisions) {
-        this.allowIfEqualGrantedDeniedDecisions = allowIfEqualGrantedDeniedDecisions;
-    }
-
-    public boolean isAllowIfEqualGrantedDeniedDecisions() {
-        return allowIfEqualGrantedDeniedDecisions;
-    }
-
     /**
      * This concrete implementation simply polls all configured  {@link
      * AccessDecisionVoter}s and upon completion determines the consensus of
@@ -111,14 +99,18 @@ public class ConsensusBased extends AbstractAccessDecisionManager {
         }
 
         if (deny > grant) {
-            throw new AccessDeniedException("Access is denied.");
+            throw new AccessDeniedException(messages.getMessage(
+                    "AbstractAccessDecisionManager.accessDenied",
+                    "Access is denied"));
         }
 
         if ((grant == deny) && (grant != 0)) {
             if (this.allowIfEqualGrantedDeniedDecisions) {
                 return;
             } else {
-                throw new AccessDeniedException("Access is denied.");
+                throw new AccessDeniedException(messages.getMessage(
+                        "AbstractAccessDecisionManager.accessDenied",
+                        "Access is denied"));
             }
         }
 
@@ -126,7 +118,18 @@ public class ConsensusBased extends AbstractAccessDecisionManager {
         if (this.isAllowIfAllAbstainDecisions()) {
             return;
         } else {
-            throw new AccessDeniedException("Access is denied.");
+            throw new AccessDeniedException(messages.getMessage(
+                    "AbstractAccessDecisionManager.accessDenied",
+                    "Access is denied"));
         }
     }
+
+    public boolean isAllowIfEqualGrantedDeniedDecisions() {
+        return allowIfEqualGrantedDeniedDecisions;
+    }
+
+    public void setAllowIfEqualGrantedDeniedDecisions(
+        boolean allowIfEqualGrantedDeniedDecisions) {
+        this.allowIfEqualGrantedDeniedDecisions = allowIfEqualGrantedDeniedDecisions;
+    }
 }

+ 7 - 6
core/src/main/java/org/acegisecurity/vote/UnanimousBased.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -30,9 +30,6 @@ import java.util.Iterator;
  * Simple concrete implementation of  {@link
  * org.acegisecurity.AccessDecisionManager} that  requires all voters to
  * abstain or grant access.
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class UnanimousBased extends AbstractAccessDecisionManager {
     //~ Static fields/initializers =============================================
@@ -105,7 +102,9 @@ public class UnanimousBased extends AbstractAccessDecisionManager {
         }
 
         if (deny > 0) {
-            throw new AccessDeniedException("Access is denied.");
+            throw new AccessDeniedException(messages.getMessage(
+                    "AbstractAccessDecisionManager.accessDenied",
+                    "Access is denied"));
         }
 
         // To get this far, there were no deny votes
@@ -117,7 +116,9 @@ public class UnanimousBased extends AbstractAccessDecisionManager {
         if (this.isAllowIfAllAbstainDecisions()) {
             return;
         } else {
-            throw new AccessDeniedException("Access is denied.");
+            throw new AccessDeniedException(messages.getMessage(
+                    "AbstractAccessDecisionManager.accessDenied",
+                    "Access is denied"));
         }
     }
 }

+ 4 - 0
core/src/test/java/org/acegisecurity/adapters/AuthByAdapterTests.java

@@ -23,6 +23,7 @@ import org.acegisecurity.GrantedAuthority;
 import org.acegisecurity.GrantedAuthorityImpl;
 
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import org.springframework.context.support.StaticMessageSource;
 
 
 /**
@@ -81,6 +82,7 @@ public class AuthByAdapterTests extends TestCase {
     public void testAuthByAdapterProviderNonAuthenticationMethods()
         throws Exception {
         AuthByAdapterProvider provider = new AuthByAdapterProvider();
+        provider.setMessageSource(new StaticMessageSource());
 
         try {
             provider.afterPropertiesSet();
@@ -99,6 +101,7 @@ public class AuthByAdapterTests extends TestCase {
     public void testAuthByAdapterProviderOnlyAcceptsAuthByAdapterImplementations()
         throws Exception {
         AuthByAdapterProvider provider = new AuthByAdapterProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setKey("my_password");
 
         // Should fail as UsernamePassword is not interface of AuthByAdapter
@@ -119,6 +122,7 @@ public class AuthByAdapterTests extends TestCase {
     public void testAuthByAdapterProviderRequiresCorrectKey()
         throws Exception {
         AuthByAdapterProvider provider = new AuthByAdapterProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setKey("my_password");
 
         // Should fail as PrincipalAcegiUserToken has different key

+ 10 - 0
core/src/test/java/org/acegisecurity/afterinvocation/BasicAclEntryAfterInvocationProviderTests.java

@@ -27,6 +27,7 @@ import org.acegisecurity.acl.basic.MockAclObjectIdentity;
 import org.acegisecurity.acl.basic.SimpleAclEntry;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.util.SimpleMethodInvocation;
+import org.springframework.context.support.StaticMessageSource;
 
 
 /**
@@ -65,6 +66,7 @@ public class BasicAclEntryAfterInvocationProviderTests extends TestCase {
                         SimpleAclEntry.ADMINISTRATION)});
 
         BasicAclEntryAfterInvocationProvider provider = new BasicAclEntryAfterInvocationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAclManager(aclManager);
         provider.afterPropertiesSet();
 
@@ -94,6 +96,7 @@ public class BasicAclEntryAfterInvocationProviderTests extends TestCase {
                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
 
         BasicAclEntryAfterInvocationProvider provider = new BasicAclEntryAfterInvocationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAclManager(aclManager);
         provider.afterPropertiesSet();
 
@@ -123,6 +126,7 @@ public class BasicAclEntryAfterInvocationProviderTests extends TestCase {
                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
 
         BasicAclEntryAfterInvocationProvider provider = new BasicAclEntryAfterInvocationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAclManager(aclManager);
         assertEquals(aclManager, provider.getAclManager());
         provider.afterPropertiesSet();
@@ -150,6 +154,7 @@ public class BasicAclEntryAfterInvocationProviderTests extends TestCase {
                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE), new MockAclEntry()});
 
         BasicAclEntryAfterInvocationProvider provider = new BasicAclEntryAfterInvocationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAclManager(aclManager);
         provider.afterPropertiesSet();
 
@@ -171,6 +176,7 @@ public class BasicAclEntryAfterInvocationProviderTests extends TestCase {
                         new MockAclObjectIdentity(), null, SimpleAclEntry.READ), new MockAclEntry()});
 
         BasicAclEntryAfterInvocationProvider provider = new BasicAclEntryAfterInvocationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAclManager(aclManager);
         assertEquals("AFTER_ACL_READ", provider.getProcessConfigAttribute());
         provider.setProcessConfigAttribute("AFTER_ACL_ADMIN");
@@ -202,6 +208,7 @@ public class BasicAclEntryAfterInvocationProviderTests extends TestCase {
                         SimpleAclEntry.ADMINISTRATION), new MockAclEntry()});
 
         BasicAclEntryAfterInvocationProvider provider = new BasicAclEntryAfterInvocationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAclManager(aclManager);
         assertEquals(SimpleAclEntry.READ, provider.getRequirePermission()[0]);
         provider.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION});
@@ -222,6 +229,7 @@ public class BasicAclEntryAfterInvocationProviderTests extends TestCase {
 
     public void testStartupDetectsMissingAclManager() throws Exception {
         BasicAclEntryAfterInvocationProvider provider = new BasicAclEntryAfterInvocationProvider();
+        provider.setMessageSource(new StaticMessageSource());
 
         try {
             provider.afterPropertiesSet();
@@ -234,6 +242,7 @@ public class BasicAclEntryAfterInvocationProviderTests extends TestCase {
     public void testStartupDetectsMissingProcessConfigAttribute()
         throws Exception {
         BasicAclEntryAfterInvocationProvider provider = new BasicAclEntryAfterInvocationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         AclManager aclManager = new MockAclManager("sydney", "marissa",
                 new AclEntry[] {new SimpleAclEntry("marissa",
                         new MockAclObjectIdentity(), null,
@@ -254,6 +263,7 @@ public class BasicAclEntryAfterInvocationProviderTests extends TestCase {
     public void testStartupDetectsMissingRequirePermission()
         throws Exception {
         BasicAclEntryAfterInvocationProvider provider = new BasicAclEntryAfterInvocationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         AclManager aclManager = new MockAclManager("sydney", "marissa",
                 new AclEntry[] {new SimpleAclEntry("marissa",
                         new MockAclObjectIdentity(), null,

+ 2 - 0
core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImplTests.java

@@ -21,6 +21,7 @@ import org.acegisecurity.Authentication;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.ui.WebAuthenticationDetails;
 
+import org.springframework.context.support.StaticMessageSource;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpSession;
 
@@ -39,6 +40,7 @@ public class ConcurrentSessionControllerImplTests extends TestCase {
         ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
         SessionRegistry registry = new SessionRegistryImpl();
         sc.setSessionRegistry(registry);
+        sc.setMessageSource(new StaticMessageSource());
 
         // Attempt to authenticate - it should be successful
         Authentication auth = createAuthentication("bob", "1212");

+ 13 - 0
core/src/test/java/org/acegisecurity/intercept/method/aopalliance/MethodSecurityInterceptorTests.java

@@ -41,6 +41,7 @@ import org.acegisecurity.runas.RunAsManagerImpl;
 
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.StaticMessageSource;
 
 import java.lang.reflect.Method;
 
@@ -177,6 +178,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testRejectsAccessDecisionManagersThatDoNotSupportMethodInvocation()
         throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setAccessDecisionManager(new MockAccessDecisionManagerWhichOnlySupportsStrings());
         si.setAuthenticationManager(new MockAuthenticationManager());
         si.setObjectDefinitionSource(new MockMethodDefinitionSource(false, true));
@@ -212,6 +214,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testRejectsCallsWhenObjectDefinitionSourceDoesNotSupportObject()
         throws Throwable {
         MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
+        interceptor.setMessageSource(new StaticMessageSource());
         interceptor.setObjectDefinitionSource(new MockObjectDefinitionSourceWhichOnlySupportsStrings());
         interceptor.setAccessDecisionManager(new MockAccessDecisionManager());
         interceptor.setAuthenticationManager(new MockAuthenticationManager());
@@ -228,6 +231,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
 
     public void testRejectsCallsWhenObjectIsNull() throws Throwable {
         MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
+        interceptor.setMessageSource(new StaticMessageSource());
 
         try {
             interceptor.invoke(null);
@@ -240,6 +244,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testRejectsRunAsManagersThatDoNotSupportMethodInvocation()
         throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setAccessDecisionManager(new MockAccessDecisionManager());
         si.setAuthenticationManager(new MockAuthenticationManager());
         si.setObjectDefinitionSource(new MockMethodDefinitionSource(false, true));
@@ -258,6 +263,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testStartupCheckForAccessDecisionManager()
         throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setRunAsManager(new MockRunAsManager());
         si.setAuthenticationManager(new MockAuthenticationManager());
         si.setAfterInvocationManager(new MockAfterInvocationManager());
@@ -276,6 +282,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testStartupCheckForAuthenticationManager()
         throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setAccessDecisionManager(new MockAccessDecisionManager());
         si.setRunAsManager(new MockRunAsManager());
         si.setAfterInvocationManager(new MockAfterInvocationManager());
@@ -294,6 +301,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testStartupCheckForMethodDefinitionSource()
         throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setAccessDecisionManager(new MockAccessDecisionManager());
         si.setAuthenticationManager(new MockAuthenticationManager());
 
@@ -308,6 +316,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
 
     public void testStartupCheckForRunAsManager() throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setAccessDecisionManager(new MockAccessDecisionManager());
         si.setAuthenticationManager(new MockAuthenticationManager());
         si.setRunAsManager(null); // Overriding the default
@@ -325,6 +334,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testStartupCheckForValidAfterInvocationManager()
         throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setRunAsManager(new MockRunAsManager());
         si.setAuthenticationManager(new MockAuthenticationManager());
         si.setAfterInvocationManager(new MockAfterInvocationManagerWhichOnlySupportsStrings());
@@ -342,6 +352,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testValidationFailsIfInvalidAttributePresented()
         throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setAccessDecisionManager(new MockAccessDecisionManager());
         si.setAuthenticationManager(new MockAuthenticationManager());
         si.setRunAsManager(new RunAsManagerImpl());
@@ -361,6 +372,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testValidationNotAttemptedIfIsValidateConfigAttributesSetToFalse()
         throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setAccessDecisionManager(new MockAccessDecisionManager());
         si.setAuthenticationManager(new MockAuthenticationManager());
 
@@ -376,6 +388,7 @@ public class MethodSecurityInterceptorTests extends TestCase {
     public void testValidationNotAttemptedIfMethodDefinitionSourceCannotReturnIterator()
         throws Exception {
         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setAccessDecisionManager(new MockAccessDecisionManager());
         si.setRunAsManager(new MockRunAsManager());
         si.setAuthenticationManager(new MockAuthenticationManager());

+ 3 - 0
core/src/test/java/org/acegisecurity/intercept/method/aspectj/AspectJSecurityInterceptorTests.java

@@ -30,6 +30,7 @@ import org.acegisecurity.context.SecurityContextHolder;
 import org.acegisecurity.intercept.method.MethodDefinitionMap;
 import org.acegisecurity.intercept.method.MethodDefinitionSourceEditor;
 import org.acegisecurity.providers.TestingAuthenticationToken;
+import org.springframework.context.support.StaticMessageSource;
 
 import java.lang.reflect.Method;
 
@@ -64,6 +65,7 @@ public class AspectJSecurityInterceptorTests extends TestCase {
     public void testCallbackIsInvokedWhenPermissionGranted()
         throws Exception {
         AspectJSecurityInterceptor si = new AspectJSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setApplicationEventPublisher(MockApplicationContext.getContext());
         si.setAccessDecisionManager(new MockAccessDecisionManager());
         si.setAuthenticationManager(new MockAuthenticationManager());
@@ -100,6 +102,7 @@ public class AspectJSecurityInterceptorTests extends TestCase {
     public void testCallbackIsNotInvokedWhenPermissionDenied()
         throws Exception {
         AspectJSecurityInterceptor si = new AspectJSecurityInterceptor();
+        si.setMessageSource(new StaticMessageSource());
         si.setApplicationEventPublisher(MockApplicationContext.getContext());
         si.setAccessDecisionManager(new MockAccessDecisionManager());
         si.setAuthenticationManager(new MockAuthenticationManager());

+ 6 - 0
core/src/test/java/org/acegisecurity/intercept/web/FilterSecurityInterceptorTests.java

@@ -34,6 +34,7 @@ import org.acegisecurity.context.SecurityContextHolder;
 import org.acegisecurity.context.SecurityContextImpl;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 
+import org.springframework.context.support.StaticMessageSource;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 
@@ -77,6 +78,7 @@ public class FilterSecurityInterceptorTests extends TestCase {
     public void testEnsuresAccessDecisionManagerSupportsFilterInvocationClass()
         throws Exception {
         FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
+        interceptor.setMessageSource(new StaticMessageSource());
         interceptor.setAuthenticationManager(new MockAuthenticationManager());
         interceptor.setObjectDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
         interceptor.setRunAsManager(new MockRunAsManager());
@@ -110,6 +112,7 @@ public class FilterSecurityInterceptorTests extends TestCase {
     public void testEnsuresRunAsManagerSupportsFilterInvocationClass()
         throws Exception {
         FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
+        interceptor.setMessageSource(new StaticMessageSource());
         interceptor.setAccessDecisionManager(new MockAccessDecisionManager());
         interceptor.setAuthenticationManager(new MockAuthenticationManager());
         interceptor.setObjectDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
@@ -144,6 +147,7 @@ public class FilterSecurityInterceptorTests extends TestCase {
         throws Throwable {
         // Setup the FilterSecurityInterceptor
         FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
+        interceptor.setMessageSource(new StaticMessageSource());
         interceptor.setAccessDecisionManager(new MockAccessDecisionManager());
         interceptor.setAuthenticationManager(new MockAuthenticationManager());
         interceptor.setRunAsManager(new MockRunAsManager());
@@ -183,6 +187,7 @@ public class FilterSecurityInterceptorTests extends TestCase {
 
     public void testNormalStartupAndGetter() throws Exception {
         FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
+        interceptor.setMessageSource(new StaticMessageSource());
         interceptor.setAccessDecisionManager(new MockAccessDecisionManager());
         interceptor.setAuthenticationManager(new MockAuthenticationManager());
 
@@ -203,6 +208,7 @@ public class FilterSecurityInterceptorTests extends TestCase {
     public void testSuccessfulInvocation() throws Throwable {
         // Setup the FilterSecurityInterceptor
         FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
+        interceptor.setMessageSource(new StaticMessageSource());
         interceptor.setAccessDecisionManager(new MockAccessDecisionManager());
         interceptor.setAuthenticationManager(new MockAuthenticationManager());
         interceptor.setRunAsManager(new MockRunAsManager());

+ 2 - 1
core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java

@@ -29,6 +29,7 @@ import org.acegisecurity.concurrent.NullConcurrentSessionController;
 
 import org.springframework.context.ApplicationEvent;
 import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.support.StaticMessageSource;
 
 
 /**
@@ -182,8 +183,8 @@ public class ProviderManagerTests extends TestCase {
 
         ProviderManager mgr = new ProviderManager();
         mgr.setProviders(providers);
+        mgr.setMessageSource(new StaticMessageSource());
         
-
         mgr.afterPropertiesSet();
         return mgr;
     }

+ 7 - 0
core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProviderTests.java

@@ -22,6 +22,7 @@ import org.acegisecurity.BadCredentialsException;
 import org.acegisecurity.GrantedAuthority;
 import org.acegisecurity.GrantedAuthorityImpl;
 import org.acegisecurity.providers.TestingAuthenticationToken;
+import org.springframework.context.support.StaticMessageSource;
 
 
 /**
@@ -53,6 +54,7 @@ public class AnonymousAuthenticationProviderTests extends TestCase {
 
     public void testDetectsAnInvalidKey() throws Exception {
         AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
         aap.setKey("qwerty");
 
         AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("WRONG_KEY",
@@ -71,6 +73,7 @@ public class AnonymousAuthenticationProviderTests extends TestCase {
 
     public void testDetectsMissingKey() throws Exception {
         AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
 
         try {
             aap.afterPropertiesSet();
@@ -82,6 +85,7 @@ public class AnonymousAuthenticationProviderTests extends TestCase {
 
     public void testGettersSetters() throws Exception {
         AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
         aap.setKey("qwerty");
         aap.afterPropertiesSet();
         assertEquals("qwerty", aap.getKey());
@@ -89,6 +93,7 @@ public class AnonymousAuthenticationProviderTests extends TestCase {
 
     public void testIgnoresClassesItDoesNotSupport() throws Exception {
         AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
         aap.setKey("qwerty");
 
         TestingAuthenticationToken token = new TestingAuthenticationToken("user",
@@ -102,6 +107,7 @@ public class AnonymousAuthenticationProviderTests extends TestCase {
 
     public void testNormalOperation() throws Exception {
         AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
         aap.setKey("qwerty");
 
         AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("qwerty",
@@ -116,6 +122,7 @@ public class AnonymousAuthenticationProviderTests extends TestCase {
 
     public void testSupports() {
         AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
         assertTrue(aap.supports(AnonymousAuthenticationToken.class));
         assertFalse(aap.supports(TestingAuthenticationToken.class));
     }

+ 14 - 0
core/src/test/java/org/acegisecurity/providers/cas/CasAuthenticationProviderTests.java

@@ -28,6 +28,7 @@ import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.providers.cas.ticketvalidator.AbstractTicketValidator;
 import org.acegisecurity.providers.dao.User;
 import org.acegisecurity.ui.cas.CasProcessingFilter;
+import org.springframework.context.support.StaticMessageSource;
 
 import java.util.HashMap;
 import java.util.List;
@@ -64,6 +65,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testAuthenticateStateful() throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider(true));
         cap.setKey("qwerty");
@@ -108,6 +110,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testAuthenticateStateless() throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider(true));
         cap.setKey("qwerty");
@@ -144,6 +147,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testDetectsAMissingTicketId() throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider(true));
         cap.setKey("qwerty");
@@ -167,6 +171,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testDetectsAnInvalidKey() throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider(true));
         cap.setKey("qwerty");
@@ -193,6 +198,7 @@ public class CasAuthenticationProviderTests extends TestCase {
     public void testDetectsMissingAuthoritiesPopulator()
         throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasProxyDecider(new MockProxyDecider());
         cap.setKey("qwerty");
         cap.setStatelessTicketCache(new MockStatelessTicketCache());
@@ -209,6 +215,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testDetectsMissingKey() throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider());
         cap.setStatelessTicketCache(new MockStatelessTicketCache());
@@ -225,6 +232,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testDetectsMissingProxyDecider() throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setKey("qwerty");
         cap.setStatelessTicketCache(new MockStatelessTicketCache());
@@ -241,6 +249,7 @@ public class CasAuthenticationProviderTests extends TestCase {
     public void testDetectsMissingStatelessTicketCache()
         throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider());
         cap.setKey("qwerty");
@@ -257,6 +266,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testDetectsMissingTicketValidator() throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider(true));
         cap.setKey("qwerty");
@@ -272,6 +282,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testGettersSetters() throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider());
         cap.setKey("qwerty");
@@ -288,6 +299,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testIgnoresClassesItDoesNotSupport() throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider());
         cap.setKey("qwerty");
@@ -307,6 +319,7 @@ public class CasAuthenticationProviderTests extends TestCase {
     public void testIgnoresUsernamePasswordAuthenticationTokensWithoutCasIdentifiersAsPrincipal()
         throws Exception {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         cap.setCasAuthoritiesPopulator(new MockAuthoritiesPopulator());
         cap.setCasProxyDecider(new MockProxyDecider());
         cap.setKey("qwerty");
@@ -322,6 +335,7 @@ public class CasAuthenticationProviderTests extends TestCase {
 
     public void testSupports() {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
+        cap.setMessageSource(new StaticMessageSource());
         assertTrue(cap.supports(UsernamePasswordAuthenticationToken.class));
         assertTrue(cap.supports(CasAuthenticationToken.class));
     }

+ 14 - 8
core/src/test/java/org/acegisecurity/providers/cas/proxy/NamedCasProxyDeciderTests.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -19,15 +19,14 @@ import junit.framework.TestCase;
 
 import org.acegisecurity.providers.cas.ProxyUntrustedException;
 
+import org.springframework.context.support.StaticMessageSource;
+
 import java.util.List;
 import java.util.Vector;
 
 
 /**
  * Tests {@link NamedCasProxyDecider}.
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class NamedCasProxyDeciderTests extends TestCase {
     //~ Constructors ===========================================================
@@ -42,17 +41,18 @@ public class NamedCasProxyDeciderTests extends TestCase {
 
     //~ Methods ================================================================
 
-    public final void setUp() throws Exception {
-        super.setUp();
-    }
-
     public static void main(String[] args) {
         junit.textui.TestRunner.run(NamedCasProxyDeciderTests.class);
     }
 
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
     public void testAcceptsIfNearestProxyIsAuthorized()
         throws Exception {
         NamedCasProxyDecider proxyDecider = new NamedCasProxyDecider();
+        proxyDecider.setMessageSource(new StaticMessageSource());
 
         // Build the ticket returned from CAS
         List proxyList = new Vector();
@@ -72,6 +72,8 @@ public class NamedCasProxyDeciderTests extends TestCase {
 
     public void testAcceptsIfNoProxiesInTicket() {
         NamedCasProxyDecider proxyDecider = new NamedCasProxyDecider();
+        proxyDecider.setMessageSource(new StaticMessageSource());
+
         List proxyList = new Vector(); // no proxies in list
 
         proxyDecider.confirmProxyListTrusted(proxyList);
@@ -80,6 +82,7 @@ public class NamedCasProxyDeciderTests extends TestCase {
 
     public void testDetectsMissingValidProxiesList() throws Exception {
         NamedCasProxyDecider proxyDecider = new NamedCasProxyDecider();
+        proxyDecider.setMessageSource(new StaticMessageSource());
 
         try {
             proxyDecider.afterPropertiesSet();
@@ -92,6 +95,7 @@ public class NamedCasProxyDeciderTests extends TestCase {
 
     public void testDoesNotAcceptNull() {
         NamedCasProxyDecider proxyDecider = new NamedCasProxyDecider();
+        proxyDecider.setMessageSource(new StaticMessageSource());
 
         try {
             proxyDecider.confirmProxyListTrusted(null);
@@ -103,6 +107,7 @@ public class NamedCasProxyDeciderTests extends TestCase {
 
     public void testGettersSetters() {
         NamedCasProxyDecider proxyDecider = new NamedCasProxyDecider();
+        proxyDecider.setMessageSource(new StaticMessageSource());
 
         // Build the list of valid nearest proxies
         List validProxies = new Vector();
@@ -117,6 +122,7 @@ public class NamedCasProxyDeciderTests extends TestCase {
     public void testRejectsIfNearestProxyIsNotAuthorized()
         throws Exception {
         NamedCasProxyDecider proxyDecider = new NamedCasProxyDecider();
+        proxyDecider.setMessageSource(new StaticMessageSource());
 
         // Build the ticket returned from CAS
         List proxyList = new Vector();

+ 2 - 0
core/src/test/java/org/acegisecurity/providers/cas/proxy/RejectProxyTicketsTests.java

@@ -18,6 +18,7 @@ package org.acegisecurity.providers.cas.proxy;
 import junit.framework.TestCase;
 
 import org.acegisecurity.providers.cas.ProxyUntrustedException;
+import org.springframework.context.support.StaticMessageSource;
 
 import java.util.List;
 import java.util.Vector;
@@ -71,6 +72,7 @@ public class RejectProxyTicketsTests extends TestCase {
 
     public void testRejectsIfAnyProxyInList() {
         RejectProxyTickets proxyDecider = new RejectProxyTickets();
+        proxyDecider.setMessageSource(new StaticMessageSource());
         List proxyList = new Vector();
         proxyList.add("https://localhost/webApp/j_acegi_cas_security_check");
 

+ 23 - 0
core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java

@@ -34,6 +34,7 @@ import org.acegisecurity.providers.dao.cache.NullUserCache;
 import org.acegisecurity.providers.dao.salt.SystemWideSaltSource;
 import org.acegisecurity.providers.encoding.ShaPasswordEncoder;
 
+import org.springframework.context.support.StaticMessageSource;
 import org.springframework.dao.DataAccessException;
 import org.springframework.dao.DataRetrievalFailureException;
 
@@ -63,6 +64,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "KOala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         provider.setUserCache(new MockUserCache());
 
@@ -79,6 +81,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "opal");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserPeterAccountExpired());
         provider.setUserCache(new MockUserCache());
 
@@ -95,6 +98,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "opal");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserPeterAccountLocked());
         provider.setUserCache(new MockUserCache());
 
@@ -111,6 +115,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "opal");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserPeterCredentialsExpired());
         provider.setUserCache(new MockUserCache());
 
@@ -138,6 +143,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "opal");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserPeter());
         provider.setUserCache(new MockUserCache());
 
@@ -154,6 +160,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoSimulateBackendError());
         provider.setUserCache(new MockUserCache());
 
@@ -170,6 +177,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         provider.setUserCache(new MockUserCache());
 
@@ -186,6 +194,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "INVALID_PASSWORD");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         provider.setUserCache(new MockUserCache());
 
@@ -202,6 +211,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setHideUserNotFoundExceptions(false); // we want UsernameNotFoundExceptions
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         provider.setUserCache(new MockUserCache());
@@ -219,6 +229,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         assertTrue(provider.isHideUserNotFoundExceptions());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         provider.setUserCache(new MockUserCache());
@@ -236,6 +247,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         provider.setUserCache(new MockUserCache());
 
@@ -253,6 +265,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
         token.setDetails("192.168.0.1");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         provider.setUserCache(new MockUserCache());
 
@@ -276,6 +289,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         provider.setUserCache(new MockUserCache());
 
@@ -305,6 +319,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
         salt.setSystemWideSalt("SYSTEM_SALT_VALUE");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissaWithSalt());
         provider.setSaltSource(salt);
         provider.setUserCache(new MockUserCache());
@@ -330,6 +345,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         provider.setUserCache(new MockUserCache());
         provider.setForcePrincipalAsString(true);
@@ -351,6 +367,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoReturnsNull());
 
         try {
@@ -364,6 +381,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
 
     public void testGettersSetters() {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setPasswordEncoder(new ShaPasswordEncoder());
         assertEquals(ShaPasswordEncoder.class,
             provider.getPasswordEncoder().getClass());
@@ -388,6 +406,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
         MockAuthenticationDaoUserMarissa authenticationDao = new MockAuthenticationDaoUserMarissa();
         MockUserCache cache = new MockUserCache();
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(authenticationDao);
         provider.setUserCache(cache);
 
@@ -414,6 +433,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
     public void testStartupFailsIfNoAuthenticationDao()
         throws Exception {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
 
         try {
             provider.afterPropertiesSet();
@@ -425,6 +445,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
 
     public void testStartupFailsIfNoUserCacheSet() throws Exception {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
         assertEquals(NullUserCache.class, provider.getUserCache().getClass());
         provider.setUserCache(null);
@@ -439,6 +460,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
 
     public void testStartupSuccess() throws Exception {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         AuthenticationDao dao = new MockAuthenticationDaoUserMarissa();
         provider.setAuthenticationDao(dao);
         provider.setUserCache(new MockUserCache());
@@ -449,6 +471,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
 
     public void testSupports() {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         assertTrue(provider.supports(UsernamePasswordAuthenticationToken.class));
         assertTrue(!provider.supports(TestingAuthenticationToken.class));
     }

+ 6 - 0
core/src/test/java/org/acegisecurity/providers/rememberme/RememberMeAuthenticationProviderTests.java

@@ -22,6 +22,7 @@ import org.acegisecurity.BadCredentialsException;
 import org.acegisecurity.GrantedAuthority;
 import org.acegisecurity.GrantedAuthorityImpl;
 import org.acegisecurity.providers.TestingAuthenticationToken;
+import org.springframework.context.support.StaticMessageSource;
 
 
 /**
@@ -53,6 +54,7 @@ public class RememberMeAuthenticationProviderTests extends TestCase {
 
     public void testDetectsAnInvalidKey() throws Exception {
         RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
         aap.setKey("qwerty");
 
         RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("WRONG_KEY",
@@ -71,6 +73,7 @@ public class RememberMeAuthenticationProviderTests extends TestCase {
 
     public void testDetectsMissingKey() throws Exception {
         RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
 
         try {
             aap.afterPropertiesSet();
@@ -82,6 +85,7 @@ public class RememberMeAuthenticationProviderTests extends TestCase {
 
     public void testGettersSetters() throws Exception {
         RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
         aap.setKey("qwerty");
         aap.afterPropertiesSet();
         assertEquals("qwerty", aap.getKey());
@@ -102,6 +106,7 @@ public class RememberMeAuthenticationProviderTests extends TestCase {
 
     public void testNormalOperation() throws Exception {
         RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
         aap.setKey("qwerty");
 
         RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("qwerty",
@@ -116,6 +121,7 @@ public class RememberMeAuthenticationProviderTests extends TestCase {
 
     public void testSupports() {
         RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
+        aap.setMessageSource(new StaticMessageSource());
         assertTrue(aap.supports(RememberMeAuthenticationToken.class));
         assertFalse(aap.supports(TestingAuthenticationToken.class));
     }

+ 6 - 0
core/src/test/java/org/acegisecurity/providers/x509/X509AuthenticationProviderTests.java

@@ -20,6 +20,7 @@ import junit.framework.TestCase;
 import org.acegisecurity.*;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import org.acegisecurity.providers.dao.User;
+import org.springframework.context.support.StaticMessageSource;
 
 import java.security.cert.X509Certificate;
 
@@ -49,6 +50,7 @@ public class X509AuthenticationProviderTests extends TestCase {
 
     public void testAuthenticationIsNullWithUnsupportedToken() {
         X509AuthenticationProvider provider = new X509AuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         Authentication request = new UsernamePasswordAuthenticationToken("dummy",
                 "dummy");
         Authentication result = provider.authenticate(request);
@@ -57,6 +59,7 @@ public class X509AuthenticationProviderTests extends TestCase {
 
     public void testFailsWithNullCertificate() {
         X509AuthenticationProvider provider = new X509AuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
 
         provider.setX509AuthoritiesPopulator(new MockAuthoritiesPopulator(false));
 
@@ -70,6 +73,7 @@ public class X509AuthenticationProviderTests extends TestCase {
 
     public void testNormalOperation() throws Exception {
         X509AuthenticationProvider provider = new X509AuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
 
         provider.setX509AuthoritiesPopulator(new MockAuthoritiesPopulator(false));
         provider.afterPropertiesSet();
@@ -82,6 +86,7 @@ public class X509AuthenticationProviderTests extends TestCase {
 
     public void testPopulatorRejectionCausesFailure() throws Exception {
         X509AuthenticationProvider provider = new X509AuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
         provider.setX509AuthoritiesPopulator(new MockAuthoritiesPopulator(true));
 
         try {
@@ -94,6 +99,7 @@ public class X509AuthenticationProviderTests extends TestCase {
 
     public void testRequiresPopulator() throws Exception {
         X509AuthenticationProvider provider = new X509AuthenticationProvider();
+        provider.setMessageSource(new StaticMessageSource());
 
         try {
             provider.afterPropertiesSet();

+ 7 - 0
core/src/test/java/org/acegisecurity/providers/x509/populator/DaoX509AuthoritiesPopulatorTests.java

@@ -26,6 +26,7 @@ import org.acegisecurity.providers.dao.User;
 import org.acegisecurity.providers.dao.UsernameNotFoundException;
 import org.acegisecurity.providers.x509.X509TestUtils;
 
+import org.springframework.context.support.StaticMessageSource;
 import org.springframework.dao.DataAccessException;
 
 import java.security.cert.X509Certificate;
@@ -56,6 +57,7 @@ public class DaoX509AuthoritiesPopulatorTests extends TestCase {
     public void testDefaultCNPatternMatch() throws Exception {
         X509Certificate cert = X509TestUtils.buildTestCertificate();
         DaoX509AuthoritiesPopulator populator = new DaoX509AuthoritiesPopulator();
+        populator.setMessageSource(new StaticMessageSource());
 
         populator.setAuthenticationDao(new MockAuthenticationDaoMatchesNameOrEmail());
         populator.afterPropertiesSet();
@@ -65,6 +67,7 @@ public class DaoX509AuthoritiesPopulatorTests extends TestCase {
     public void testEmailPatternMatch() throws Exception {
         X509Certificate cert = X509TestUtils.buildTestCertificate();
         DaoX509AuthoritiesPopulator populator = new DaoX509AuthoritiesPopulator();
+        populator.setMessageSource(new StaticMessageSource());
 
         populator.setAuthenticationDao(new MockAuthenticationDaoMatchesNameOrEmail());
         populator.setSubjectDNRegex("emailAddress=(.*?),");
@@ -74,6 +77,7 @@ public class DaoX509AuthoritiesPopulatorTests extends TestCase {
 
     public void testInvalidRegexFails() throws Exception {
         DaoX509AuthoritiesPopulator populator = new DaoX509AuthoritiesPopulator();
+        populator.setMessageSource(new StaticMessageSource());
         populator.setAuthenticationDao(new MockAuthenticationDaoMatchesNameOrEmail());
         populator.setSubjectDNRegex("CN=(.*?,"); // missing closing bracket on group
 
@@ -88,6 +92,7 @@ public class DaoX509AuthoritiesPopulatorTests extends TestCase {
     public void testMatchOnShoeSizeFieldInDNFails() throws Exception {
         X509Certificate cert = X509TestUtils.buildTestCertificate();
         DaoX509AuthoritiesPopulator populator = new DaoX509AuthoritiesPopulator();
+        populator.setMessageSource(new StaticMessageSource());
 
         populator.setAuthenticationDao(new MockAuthenticationDaoMatchesNameOrEmail());
         populator.setSubjectDNRegex("shoeSize=(.*?),");
@@ -104,6 +109,7 @@ public class DaoX509AuthoritiesPopulatorTests extends TestCase {
     public void testPatternWithNoGroupFails() throws Exception {
         X509Certificate cert = X509TestUtils.buildTestCertificate();
         DaoX509AuthoritiesPopulator populator = new DaoX509AuthoritiesPopulator();
+        populator.setMessageSource(new StaticMessageSource());
 
         populator.setAuthenticationDao(new MockAuthenticationDaoMatchesNameOrEmail());
         populator.setSubjectDNRegex("CN=.*?,");
@@ -120,6 +126,7 @@ public class DaoX509AuthoritiesPopulatorTests extends TestCase {
 
     public void testRequiresDao() throws Exception {
         DaoX509AuthoritiesPopulator populator = new DaoX509AuthoritiesPopulator();
+        populator.setMessageSource(new StaticMessageSource());
 
         try {
             populator.afterPropertiesSet();

+ 9 - 8
core/src/test/java/org/acegisecurity/runas/RunAsImplAuthenticationProviderTests.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -21,15 +21,15 @@ import org.acegisecurity.Authentication;
 import org.acegisecurity.BadCredentialsException;
 import org.acegisecurity.GrantedAuthority;
 import org.acegisecurity.GrantedAuthorityImpl;
+
 import org.acegisecurity.providers.TestingAuthenticationToken;
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 
+import org.springframework.context.support.StaticMessageSource;
+
 
 /**
  * Tests {@link RunAsImplAuthenticationProvider}.
- *
- * @author Ben Alex
- * @version $Id$
  */
 public class RunAsImplAuthenticationProviderTests extends TestCase {
     //~ Constructors ===========================================================
@@ -44,14 +44,14 @@ public class RunAsImplAuthenticationProviderTests extends TestCase {
 
     //~ Methods ================================================================
 
-    public final void setUp() throws Exception {
-        super.setUp();
-    }
-
     public static void main(String[] args) {
         junit.textui.TestRunner.run(RunAsImplAuthenticationProviderTests.class);
     }
 
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
     public void testAuthenticationFailDueToWrongKey() {
         RunAsUserToken token = new RunAsUserToken("WRONG_PASSWORD", "Test",
                 "Password",
@@ -59,6 +59,7 @@ public class RunAsImplAuthenticationProviderTests extends TestCase {
                         "ROLE_TWO")}, UsernamePasswordAuthenticationToken.class);
         RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider();
         provider.setKey("hello_world");
+        provider.setMessageSource(new StaticMessageSource());
 
         try {
             provider.authenticate(token);

+ 13 - 0
core/src/test/java/org/acegisecurity/ui/switchuser/SwitchUserProcessingFilterTests.java

@@ -31,6 +31,7 @@ import org.acegisecurity.providers.dao.User;
 import org.acegisecurity.providers.dao.UsernameNotFoundException;
 import org.acegisecurity.util.MockFilterChain;
 
+import org.springframework.context.support.StaticMessageSource;
 import org.springframework.dao.DataAccessException;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
@@ -74,6 +75,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
             "user-that-doesnt-exist");
 
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
 
         try {
@@ -97,6 +99,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
             "mcgarrett");
 
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
 
         try {
@@ -122,6 +125,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
             "wofat");
 
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
 
         try {
@@ -147,6 +151,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
             "steve");
 
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
 
         try {
@@ -169,6 +174,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
             "jacklord");
 
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
 
         Authentication result = filter.attemptSwitchUser(request);
@@ -177,6 +183,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
 
     public void testBadConfigMissingAuthenticationDao() {
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setSwitchUserUrl("/j_acegi_switch_user");
         filter.setExitUserUrl("/j_acegi_exit_user");
         filter.setTargetUrl("/main.jsp");
@@ -191,6 +198,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
 
     public void testBadConfigMissingTargetUrl() {
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
         filter.setSwitchUserUrl("/j_acegi_switch_user");
         filter.setExitUserUrl("/j_acegi_exit_user");
@@ -206,6 +214,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
     public void testDefaultProcessesFilterUrlWithPathParameter() {
         MockHttpServletRequest request = createMockSwitchRequest();
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setSwitchUserUrl("/j_acegi_switch_user");
 
         request.setRequestURI(
@@ -238,6 +247,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
 
         // setup filter
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
         filter.setExitUserUrl("/j_acegi_exit_user");
 
@@ -266,6 +276,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
 
         // setup filter
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
         filter.setExitUserUrl("/j_acegi_exit_user");
 
@@ -294,6 +305,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
         MockFilterChain chain = new MockFilterChain(true);
 
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setSwitchUserUrl("/j_acegi_switch_user");
         filter.setTargetUrl("/webapp/someOtherUrl");
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
@@ -343,6 +355,7 @@ public class SwitchUserProcessingFilterTests extends TestCase {
 
         // setup filter
         SwitchUserProcessingFilter filter = new SwitchUserProcessingFilter();
+        filter.setMessageSource(new StaticMessageSource());
         filter.setAuthenticationDao(new MockAuthenticationDaoUserJackLord());
         filter.setSwitchUserUrl("/j_acegi_switch_user");
 

+ 2 - 0
core/src/test/java/org/acegisecurity/vote/AffirmativeBasedTests.java

@@ -23,6 +23,7 @@ import org.acegisecurity.GrantedAuthority;
 import org.acegisecurity.GrantedAuthorityImpl;
 import org.acegisecurity.SecurityConfig;
 import org.acegisecurity.providers.TestingAuthenticationToken;
+import org.springframework.context.support.StaticMessageSource;
 
 import java.util.List;
 import java.util.Vector;
@@ -143,6 +144,7 @@ public class AffirmativeBasedTests extends TestCase {
 
     private AffirmativeBased makeDecisionManager() {
         AffirmativeBased decisionManager = new AffirmativeBased();
+        decisionManager.setMessageSource(new StaticMessageSource());
         RoleVoter roleVoter = new RoleVoter();
         DenyVoter denyForSureVoter = new DenyVoter();
         DenyAgainVoter denyAgainForSureVoter = new DenyAgainVoter();

+ 2 - 0
core/src/test/java/org/acegisecurity/vote/ConsensusBasedTests.java

@@ -23,6 +23,7 @@ import org.acegisecurity.GrantedAuthority;
 import org.acegisecurity.GrantedAuthorityImpl;
 import org.acegisecurity.SecurityConfig;
 import org.acegisecurity.providers.TestingAuthenticationToken;
+import org.springframework.context.support.StaticMessageSource;
 
 import java.util.List;
 import java.util.Vector;
@@ -164,6 +165,7 @@ public class ConsensusBasedTests extends TestCase {
 
     private ConsensusBased makeDecisionManager() {
         ConsensusBased decisionManager = new ConsensusBased();
+        decisionManager.setMessageSource(new StaticMessageSource());
         RoleVoter roleVoter = new RoleVoter();
         DenyVoter denyForSureVoter = new DenyVoter();
         DenyAgainVoter denyAgainForSureVoter = new DenyAgainVoter();

+ 10 - 0
core/src/test/java/org/acegisecurity/vote/UnanimousBasedTests.java

@@ -23,6 +23,7 @@ import org.acegisecurity.GrantedAuthority;
 import org.acegisecurity.GrantedAuthorityImpl;
 import org.acegisecurity.SecurityConfig;
 import org.acegisecurity.providers.TestingAuthenticationToken;
+import org.springframework.context.support.StaticMessageSource;
 
 import java.util.List;
 import java.util.Vector;
@@ -59,6 +60,7 @@ public class UnanimousBasedTests extends TestCase {
         throws Exception {
         TestingAuthenticationToken auth = makeTestToken();
         UnanimousBased mgr = makeDecisionManager();
+        mgr.setMessageSource(new StaticMessageSource());
 
         ConfigAttributeDefinition config = new ConfigAttributeDefinition();
         config.addConfigAttribute(new SecurityConfig("ROLE_1")); // grant
@@ -76,6 +78,7 @@ public class UnanimousBasedTests extends TestCase {
         throws Exception {
         TestingAuthenticationToken auth = makeTestToken();
         UnanimousBased mgr = makeDecisionManager();
+        mgr.setMessageSource(new StaticMessageSource());
 
         ConfigAttributeDefinition config = new ConfigAttributeDefinition();
         config.addConfigAttribute(new SecurityConfig("ROLE_2")); // grant
@@ -88,6 +91,7 @@ public class UnanimousBasedTests extends TestCase {
         throws Exception {
         TestingAuthenticationToken auth = makeTestToken();
         UnanimousBased mgr = makeDecisionManager();
+        mgr.setMessageSource(new StaticMessageSource());
 
         ConfigAttributeDefinition config = new ConfigAttributeDefinition();
         config.addConfigAttribute(new SecurityConfig("ROLE_WE_DO_NOT_HAVE")); // deny
@@ -103,6 +107,7 @@ public class UnanimousBasedTests extends TestCase {
     public void testRoleVoterPrefixObserved() throws Exception {
         TestingAuthenticationToken auth = makeTestTokenWithFooBarPrefix();
         UnanimousBased mgr = makeDecisionManagerWithFooBarPrefix();
+        mgr.setMessageSource(new StaticMessageSource());
 
         ConfigAttributeDefinition config = new ConfigAttributeDefinition();
         config.addConfigAttribute(new SecurityConfig("FOOBAR_1")); // grant
@@ -116,6 +121,7 @@ public class UnanimousBasedTests extends TestCase {
         throws Exception {
         TestingAuthenticationToken auth = makeTestToken();
         UnanimousBased mgr = makeDecisionManager();
+        mgr.setMessageSource(new StaticMessageSource());
 
         assertTrue(!mgr.isAllowIfAllAbstainDecisions()); // check default
 
@@ -135,6 +141,7 @@ public class UnanimousBasedTests extends TestCase {
         TestingAuthenticationToken auth = makeTestToken();
         UnanimousBased mgr = makeDecisionManager();
         mgr.setAllowIfAllAbstainDecisions(true);
+        mgr.setMessageSource(new StaticMessageSource());
         assertTrue(mgr.isAllowIfAllAbstainDecisions()); // check changed
 
         ConfigAttributeDefinition config = new ConfigAttributeDefinition();
@@ -148,6 +155,7 @@ public class UnanimousBasedTests extends TestCase {
         throws Exception {
         TestingAuthenticationToken auth = makeTestToken();
         UnanimousBased mgr = makeDecisionManager();
+        mgr.setMessageSource(new StaticMessageSource());
 
         ConfigAttributeDefinition config = new ConfigAttributeDefinition();
         config.addConfigAttribute(new SecurityConfig("ROLE_1")); // grant
@@ -159,6 +167,7 @@ public class UnanimousBasedTests extends TestCase {
 
     private UnanimousBased makeDecisionManager() {
         UnanimousBased decisionManager = new UnanimousBased();
+        decisionManager.setMessageSource(new StaticMessageSource());
         RoleVoter roleVoter = new RoleVoter();
         DenyVoter denyForSureVoter = new DenyVoter();
         DenyAgainVoter denyAgainForSureVoter = new DenyAgainVoter();
@@ -173,6 +182,7 @@ public class UnanimousBasedTests extends TestCase {
 
     private UnanimousBased makeDecisionManagerWithFooBarPrefix() {
         UnanimousBased decisionManager = new UnanimousBased();
+        decisionManager.setMessageSource(new StaticMessageSource());
         RoleVoter roleVoter = new RoleVoter();
         roleVoter.setRolePrefix("FOOBAR_");