Browse Source

SEC-284: added allowEmptyPasswords property with default value "true"

Luke Taylor 19 years ago
parent
commit
02e7bbb982

+ 63 - 25
core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticationProvider.java

@@ -35,39 +35,59 @@ import org.springframework.util.StringUtils;
 
 /**
  * An {@link org.acegisecurity.providers.AuthenticationProvider} implementation that provides integration with an
- * LDAP server.<p>There are many ways in which an LDAP directory can be configured so this class delegates most of
- * its responsibilites to two separate strategy interfaces, {@link LdapAuthenticator} and {@link
- * LdapAuthoritiesPopulator}.</p>
- *  <h3>LdapAuthenticator</h3>This interface is responsible for performing the user authentication and retrieving
+ * LDAP server.
+ *
+ * <p>There are many ways in which an LDAP directory can be configured so this class delegates most of
+ * its responsibilites to two separate strategy interfaces, {@link LdapAuthenticator}
+ * and {@link LdapAuthoritiesPopulator}.</p>
+ *
+ *  <h3>LdapAuthenticator</h3>
+ * This interface is responsible for performing the user authentication and retrieving
  * the user's information from the directory. Example implementations are {@link
  * org.acegisecurity.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates the user by
  * "binding" as that user, and {@link org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator
  * PasswordComparisonAuthenticator} which performs a comparison of the supplied password with the value stored in the
- * directory, either by retrieving the password or performing an LDAP "compare" operation.<p>The task of retrieving
- * the user attributes is delegated to the authenticator because the permissions on the attributes may depend on the
- * type of authentication being used; for example, if binding as the user, it may be necessary to read them with the
- * user's own permissions (using the same context used for the bind operation).</p>
- *  <h3>LdapAuthoritiesPopulator</h3>Once the user has been authenticated, this interface is called to obtain the
- * set of granted authorities for the user. The {@link
- * org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator} can be
- * configured to obtain user role information from the user's attributes and/or to perform a search for "groups" that
- * the user is a member of and map these to roles.<p>A custom implementation could obtain the roles from a
- * completely different source, for example from a database.</p>
- *  <h3>Configuration</h3>A simple configuration might be as follows:<pre>
+ * directory, either by retrieving the password or performing an LDAP "compare" operation.
+ * <p>The task of retrieving the user attributes is delegated to the authenticator because the permissions on the
+ * attributes may depend on the type of authentication being used; for example, if binding as the user, it may be
+ * necessary to read them with the user's own permissions (using the same context used for the bind operation).</p>
+ *
+ *  <h3>LdapAuthoritiesPopulator</h3>
+ * Once the user has been authenticated, this interface is called to obtain the set of granted authorities for the
+ * user.
+ * The
+ * {@link org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator}
+ * can be configured to obtain user role information from the user's attributes and/or to perform a search for
+ * "groups" that the user is a member of and map these to roles.
+ *
+ * <p>A custom implementation could obtain the roles from a completely different source, for example from a database.
+ * </p>
+ *
+ *  <h3>Configuration</h3>A simple configuration might be as follows:
+ * <pre>
  *    &lt;bean id="initialDirContextFactory" class="org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory">
  *      &lt;constructor-arg value="ldap://monkeymachine:389/dc=acegisecurity,dc=org"/>
  *      &lt;property name="managerDn">&lt;value>cn=manager,dc=acegisecurity,dc=org&lt;/value>&lt;/property>
- *      &lt;property name="managerPassword">&lt;value>password&lt;/value>&lt;/property>   &lt;/bean>
+ *      &lt;property name="managerPassword">&lt;value>password&lt;/value>&lt;/property>
+ *    &lt;/bean>
+ *
  *    &lt;bean id="ldapAuthProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
- *    &lt;constructor-arg>     &lt;bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
- *         &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
- *         &lt;property name="userDnPatterns">&lt;list>&lt;value>uid={0},ou=people&lt;/value>&lt;/list>&lt;/property>
- *      &lt;/bean>   &lt;/constructor-arg>   &lt;constructor-arg>
- *      &lt;bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
- *         &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
- *         &lt;constructor-arg>&lt;value>ou=groups&lt;/value>&lt;/constructor-arg>
- *         &lt;property name="groupRoleAttribute">&lt;value>ou&lt;/value>&lt;/property>     &lt;/bean>
- *    &lt;/constructor-arg> &lt;/bean></pre><p>This would set up the provider to access an LDAP server with URL
+ *      &lt;constructor-arg>
+ *        &lt;bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
+ *          &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
+ *          &lt;property name="userDnPatterns">&lt;list>&lt;value>uid={0},ou=people&lt;/value>&lt;/list>&lt;/property>
+ *        &lt;/bean>
+ *      &lt;/constructor-arg>
+ *      &lt;constructor-arg>
+ *        &lt;bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
+ *          &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
+ *          &lt;constructor-arg>&lt;value>ou=groups&lt;/value>&lt;/constructor-arg>
+ *          &lt;property name="groupRoleAttribute">&lt;value>ou&lt;/value>&lt;/property>
+ *        &lt;/bean>
+ *      &lt;/constructor-arg>
+ *    &lt;/bean></pre>
+ *
+ * <p>This would set up the provider to access an LDAP server with URL
  * <tt>ldap://monkeymachine:389/dc=acegisecurity,dc=org</tt>. Authentication will be performed by attempting to bind
  * with the DN <tt>uid=&lt;user-login-name&gt;,ou=people,dc=acegisecurity,dc=org</tt>. After successful
  * authentication, roles will be assigned to the user by searching under the DN
@@ -90,6 +110,9 @@ public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticatio
     private LdapAuthenticator authenticator;
     private LdapAuthoritiesPopulator authoritiesPopulator;
 
+    /** The provider will reject an authentication request with an empty password if this is set to "true" */
+    private boolean allowEmptyPasswords = true;
+
     //~ Constructors ===================================================================================================
 
     public LdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
@@ -111,6 +134,15 @@ public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticatio
         }
     }
 
+    /**
+     * Determines whether the provider will reject empty passwords by default.
+     * This may be useful when using LDAP servers which interpret an empty password as
+     * anonymous access, even if a (possibly non-existent) principal is supplied.
+     */
+    public void setAllowEmptyPasswords(boolean allowEmptyPasswords) {
+        this.allowEmptyPasswords = allowEmptyPasswords;
+    }
+
     /**
      * Creates the final <tt>UserDetails</tt> object that will be returned by the provider once the user has
      * been authenticated.<p>The <tt>LdapAuthoritiesPopulator</tt> will be used to create the granted
@@ -157,6 +189,12 @@ public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticatio
         String password = (String) authentication.getCredentials();
         Assert.notNull(password, "Null password was supplied in authentication token");
 
+        if(!allowEmptyPasswords && password.length() == 0) {
+            logger.debug("Rejecting empty password for user " + username);
+            throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyPassword",
+                    "Empty Password"));
+        }
+
         LdapUserDetails ldapUser = authenticator.authenticate(username, password);
 
         return createUserDetails(ldapUser, username, password);

+ 1 - 0
core/src/main/resources/org/acegisecurity/messages.properties

@@ -38,6 +38,7 @@ SwitchUserProcessingFilter.expired=User account has expired
 SwitchUserProcessingFilter.credentialsExpired=User credentials have expired
 AbstractAccessDecisionManager.accessDenied=Access is denied
 LdapAuthenticationProvider.emptyUsername=Empty username not allowed
+LdapAuthenticationProvider.emptyPassword=Bad credentials
 DefaultIntitalDirContextFactory.communicationFailure=Unable to connect to LDAP server
 DefaultIntitalDirContextFactory.badCredentials=Bad credentials
 DefaultIntitalDirContextFactory.unexpectedException=Failed to obtain InitialDirContext due to unexpected exception

+ 27 - 4
core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java

@@ -86,6 +86,23 @@ public class LdapAuthenticationProviderTests extends TestCase {
         } catch (BadCredentialsException expected) {}
     }
 
+    public void testEmptyPasswordIsAcceptedByDefault() {
+        LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
+                new MockAuthoritiesPopulator());
+        ldapProvider.retrieveUser("jen", new UsernamePasswordAuthenticationToken("jen", ""));
+    }
+
+    public void testEmptyPasswordIsRejectedWhenFlagIsSet() {
+        LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
+                new MockAuthoritiesPopulator());
+        ldapProvider.setAllowEmptyPasswords(false);
+
+        try {
+            ldapProvider.retrieveUser("jen", new UsernamePasswordAuthenticationToken("jen", ""));
+            fail("Expected BadCredentialsException for empty password");
+        } catch (BadCredentialsException expected) {}
+    }
+
     public void testNormalUsage() {
         LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
                 new MockAuthoritiesPopulator());
@@ -114,17 +131,23 @@ public class LdapAuthenticationProviderTests extends TestCase {
         Attributes userAttributes = new BasicAttributes("cn", "bob");
 
         public LdapUserDetails authenticate(String username, String password) {
+            LdapUserDetailsImpl.Essence userEssence = new LdapUserDetailsImpl.Essence();
+            userEssence.setPassword("{SHA}anencodedpassword");
+            userEssence.setAttributes(userAttributes);
+
             if (username.equals("bob") && password.equals("bobspassword")) {
-                LdapUserDetailsImpl.Essence userEssence = new LdapUserDetailsImpl.Essence();
                 userEssence.setDn("cn=bob,ou=people,dc=acegisecurity,dc=org");
-                userEssence.setPassword("{SHA}anencodedpassword");
-                userEssence.setAttributes(userAttributes);
+                userEssence.addAuthority(new GrantedAuthorityImpl("ROLE_FROM_ENTRY"));
+
+                return userEssence.createUserDetails();
+            } else if (username.equals("jen") && password.equals("")) {
+                userEssence.setDn("cn=jen,ou=people,dc=acegisecurity,dc=org");
                 userEssence.addAuthority(new GrantedAuthorityImpl("ROLE_FROM_ENTRY"));
 
                 return userEssence.createUserDetails();
             }
 
-            throw new BadCredentialsException("Authentication of Bob failed.");
+            throw new BadCredentialsException("Authentication failed.");
         }
     }