From f02cf43c950f9263fd6a16200b17225549be3c1f Mon Sep 17 00:00:00 2001 From: Ulrik Sandberg Date: Mon, 14 Dec 2009 20:45:30 +0000 Subject: [PATCH] First shot at authentication methods that provide the actual exception (LDAP-192). --- .../core/AuthenticationErrorCallback.java | 20 ++++++ .../ldap/core/LdapOperations.java | 54 +++++++++++++++ .../ldap/core/LdapTemplate.java | 66 +++++++++++++++++-- .../ldap/support/LdapUtils.java | 2 +- .../ldap/LdapTemplateAuthenticationITest.java | 17 +++++ 5 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/org/springframework/ldap/core/AuthenticationErrorCallback.java diff --git a/core/src/main/java/org/springframework/ldap/core/AuthenticationErrorCallback.java b/core/src/main/java/org/springframework/ldap/core/AuthenticationErrorCallback.java new file mode 100644 index 00000000..ebbde365 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/core/AuthenticationErrorCallback.java @@ -0,0 +1,20 @@ +package org.springframework.ldap.core; + +/** + * Callback interface to be used in the authentication methods in + * {@link LdapOperations} for performing operations when there + * are authentication errors. Can be useful when the cause of the + * authentication failure needs to be retrieved. + * + * @author Ulrik Sandberg + * @since 1.3.1 + */ +public interface AuthenticationErrorCallback { + /** + * This method will be called with the authentication exception in + * case there is a problem with the authentication. + * + * @param e the exception that was caught in the authentication method + */ + void execute(Exception e); +} diff --git a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java index d9a3d9dc..6c4b5b5b 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java @@ -1392,6 +1392,60 @@ public interface LdapOperations { */ boolean authenticate(String base, String filter, String password, AuthenticatedLdapEntryContextCallback callback); + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. The resulting DirContext instance is then used as input to the + * supplied {@link AuthenticatedLdapEntryContextCallback} to perform any + * additional LDAP operations against the authenticated DirContext. If an + * exception is caught, the same exception is passed on to the given + * {@link AuthenticationErrorCallback}. This enables the caller to provide a + * callback that, for example, collects the exception for later processing. + * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @param callback the callback that will be called to perform operations + * on the DirContext authenticated with the found user. + * @param errorCallback the callback that will be called if an exception is caught. + * @return true if the authentication was successful, + * false otherwise. + * @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback) + * @since 1.3.1 + */ + boolean authenticate(Name base, String filter, String password, + AuthenticatedLdapEntryContextCallback callback, + AuthenticationErrorCallback errorCallback); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. The resulting DirContext instance is then used as input to the + * supplied {@link AuthenticatedLdapEntryContextCallback} to perform any + * additional LDAP operations against the authenticated DirContext. If an + * exception is caught, the same exception is passed on to the given + * {@link AuthenticationErrorCallback}. This enables the caller to provide a + * callback that, for example, collects the exception for later processing. + * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @param callback the callback that will be called to perform operations + * on the DirContext authenticated with the found user. + * @param errorCallback the callback that will be called if an exception is caught. + * @return true if the authentication was successful, + * false otherwise. + * @see #authenticate(String, String, String, AuthenticatedLdapEntryContextCallback) + * @since 1.3.1 + */ + boolean authenticate(String base, String filter, String password, + AuthenticatedLdapEntryContextCallback callback, + AuthenticationErrorCallback errorCallback); + /** * Perform a search for a unique entry matching the specified search * criteria and return the found object. If no entry is found or if there diff --git a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java index c830cbc1..1f702a57 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java @@ -1383,7 +1383,9 @@ public class LdapTemplate implements LdapOperations, InitializingBean { * .Name, java.lang.String, java.lang.String) */ public boolean authenticate(Name base, String filter, String password) { - return authenticate(base, filter, password, new NullAuthenticatedLdapEntryContextCallback()); + return authenticate(base, filter, password, + new NullAuthenticatedLdapEntryContextCallback(), + new NullAuthenticationErrorCallback()); } /* @@ -1394,7 +1396,9 @@ public class LdapTemplate implements LdapOperations, InitializingBean { * , java.lang.String, java.lang.String) */ public boolean authenticate(String base, String filter, String password) { - return authenticate(base, filter, password, new NullAuthenticatedLdapEntryContextCallback()); + return authenticate(new DistinguishedName(base), filter, password, + new NullAuthenticatedLdapEntryContextCallback(), + new NullAuthenticationErrorCallback()); } /* @@ -1407,7 +1411,7 @@ public class LdapTemplate implements LdapOperations, InitializingBean { */ public boolean authenticate(String base, String filter, String password, AuthenticatedLdapEntryContextCallback callback) { - return authenticate(new DistinguishedName(base), filter, password, callback); + return authenticate(new DistinguishedName(base), filter, password, callback, new NullAuthenticationErrorCallback()); } /* @@ -1420,6 +1424,34 @@ public class LdapTemplate implements LdapOperations, InitializingBean { */ public boolean authenticate(Name base, String filter, String password, final AuthenticatedLdapEntryContextCallback callback) { + return authenticate(base, filter, password, callback, new NullAuthenticationErrorCallback()); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.ldap.core.LdapOperations#authenticate(java.lang.String + * , java.lang.String, java.lang.String, + * org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback, + * org.springframework.ldap.core.AuthenticationErrorCallback) + */ + public boolean authenticate(String base, String filter, String password, + final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) { + return authenticate(new DistinguishedName(base), filter, password, callback, errorCallback); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.ldap.core.LdapOperations#authenticate(javax.naming + * .Name, java.lang.String, java.lang.String, + * org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback, + * org.springframework.ldap.core.AuthenticationErrorCallback) + */ + public boolean authenticate(Name base, String filter, String password, + final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) { List result = search(base, filter, new LdapEntryIdentificationContextMapper()); if (result.size() != 1) { @@ -1442,6 +1474,7 @@ public class LdapTemplate implements LdapOperations, InitializingBean { } catch (Exception e) { log.error("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteDn() + "'", e); + errorCallback.execute(e); return false; } } @@ -1476,10 +1509,31 @@ public class LdapTemplate implements LdapOperations, InitializingBean { return searchForObject(new DistinguishedName(base), filter, mapper); } - private static final class NullAuthenticatedLdapEntryContextCallback implements - AuthenticatedLdapEntryContextCallback { - public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { + public static final class NullAuthenticatedLdapEntryContextCallback + implements AuthenticatedLdapEntryContextCallback { + public void executeWithContext(DirContext ctx, + LdapEntryIdentification ldapEntryIdentification) { // Do nothing } } + + public static final class NullAuthenticationErrorCallback + implements AuthenticationErrorCallback { + public void execute(Exception e) { + // Do nothing + } + } + + public static final class CollectingErrorCallback implements + AuthenticationErrorCallback { + private Exception error; + + public void execute(Exception e) { + this.error = e; + } + + public Exception getError() { + return error; + } + } } diff --git a/core/src/main/java/org/springframework/ldap/support/LdapUtils.java b/core/src/main/java/org/springframework/ldap/support/LdapUtils.java index 20ee0174..4c712b51 100644 --- a/core/src/main/java/org/springframework/ldap/support/LdapUtils.java +++ b/core/src/main/java/org/springframework/ldap/support/LdapUtils.java @@ -401,7 +401,7 @@ public final class LdapUtils { * href="http://blogs.msdn.com/oldnewthing/archive/2004/03/15/89753.aspx" * >here. * - * @param sid SID in readable format + * @param string SID in readable format * @return Binary version of the given sid * @see LdapUtils#convertBinarySidToString(byte[]) * @since 1.3.1 diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAuthenticationITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAuthenticationITest.java index 71fd4cf9..8b7a78c8 100644 --- a/test/integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAuthenticationITest.java +++ b/test/integration-tests/src/test/java/org/springframework/ldap/LdapTemplateAuthenticationITest.java @@ -18,10 +18,14 @@ package org.springframework.ldap; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback; import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.LdapTemplate.CollectingErrorCallback; +import org.springframework.ldap.core.LdapTemplate.NullAuthenticatedLdapEntryContextCallback; import org.springframework.ldap.filter.AndFilter; import org.springframework.ldap.filter.EqualsFilter; import org.springframework.ldap.filter.WhitespaceWildcardsFilter; @@ -53,6 +57,19 @@ public class LdapTemplateAuthenticationITest extends AbstractLdapTemplateIntegra assertFalse(tested.authenticate("", filter.toString(), "invalidpassword")); } + @Test + public void testAuthenticateWithInvalidPasswordAndCollectedException() { + AndFilter filter = new AndFilter(); + filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3")); + final CollectingErrorCallback errorCallback = new CollectingErrorCallback(); + final AuthenticatedLdapEntryContextCallback callback = new NullAuthenticatedLdapEntryContextCallback(); + assertFalse(tested.authenticate("", filter.toString(), "invalidpassword", callback, errorCallback)); + final Exception error = errorCallback.getError(); + assertNotNull("collected error should not be null", error); + assertTrue("expected org.springframework.ldap.AuthenticationException", error instanceof AuthenticationException); + assertTrue("expected javax.naming.AuthenticationException", error.getCause() instanceof javax.naming.AuthenticationException); + } + @Test public void testAuthenticateWithFilterThatDoesNotMatchAnything() { AndFilter filter = new AndFilter();