From 02e7bbb98235987a258d9f2db7b65c1438b297e5 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Wed, 31 May 2006 15:00:59 +0000 Subject: [PATCH] SEC-284: added allowEmptyPasswords property with default value "true" --- .../ldap/LdapAuthenticationProvider.java | 88 +++++++++++++------ .../org/acegisecurity/messages.properties | 1 + .../ldap/LdapAuthenticationProviderTests.java | 31 ++++++- 3 files changed, 91 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticationProvider.java index 1c82258df5..92b5bb9bb4 100644 --- a/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticationProvider.java +++ b/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.

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}.

- *

LdapAuthenticator

This interface is responsible for performing the user authentication and retrieving + * LDAP server. + * + *

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}.

+ * + *

LdapAuthenticator

+ * 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.

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).

- *

LdapAuthoritiesPopulator

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.

A custom implementation could obtain the roles from a - * completely different source, for example from a database.

- *

Configuration

A simple configuration might be as follows:
+ * directory, either by retrieving the password or performing an LDAP "compare" operation.
+ * 

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).

+ * + *

LdapAuthoritiesPopulator

+ * 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. + * + *

A custom implementation could obtain the roles from a completely different source, for example from a database. + *

+ * + *

Configuration

A simple configuration might be as follows: + *
  *    <bean id="initialDirContextFactory" class="org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory">
  *      <constructor-arg value="ldap://monkeymachine:389/dc=acegisecurity,dc=org"/>
  *      <property name="managerDn"><value>cn=manager,dc=acegisecurity,dc=org</value></property>
- *      <property name="managerPassword"><value>password</value></property>   </bean>
+ *      <property name="managerPassword"><value>password</value></property>
+ *    </bean>
+ *
  *    <bean id="ldapAuthProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
- *    <constructor-arg>     <bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
- *         <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
- *         <property name="userDnPatterns"><list><value>uid={0},ou=people</value></list></property>
- *      </bean>   </constructor-arg>   <constructor-arg>
- *      <bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
- *         <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
- *         <constructor-arg><value>ou=groups</value></constructor-arg>
- *         <property name="groupRoleAttribute"><value>ou</value></property>     </bean>
- *    </constructor-arg> </bean>

This would set up the provider to access an LDAP server with URL + * <constructor-arg> + * <bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator"> + * <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg> + * <property name="userDnPatterns"><list><value>uid={0},ou=people</value></list></property> + * </bean> + * </constructor-arg> + * <constructor-arg> + * <bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator"> + * <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg> + * <constructor-arg><value>ou=groups</value></constructor-arg> + * <property name="groupRoleAttribute"><value>ou</value></property> + * </bean> + * </constructor-arg> + * </bean>

+ * + *

This would set up the provider to access an LDAP server with URL * ldap://monkeymachine:389/dc=acegisecurity,dc=org. Authentication will be performed by attempting to bind * with the DN uid=<user-login-name>,ou=people,dc=acegisecurity,dc=org. 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 UserDetails object that will be returned by the provider once the user has * been authenticated.

The LdapAuthoritiesPopulator 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); diff --git a/core/src/main/resources/org/acegisecurity/messages.properties b/core/src/main/resources/org/acegisecurity/messages.properties index 90de5e78e4..734d344548 100644 --- a/core/src/main/resources/org/acegisecurity/messages.properties +++ b/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 diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java index a64a2f0db7..4f3170e5c0 100644 --- a/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java +++ b/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."); } }