From 9e2fbbc273bb6e0082312ff664ede8e0cfe7de4a Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 11 Feb 2014 13:43:40 -0600 Subject: [PATCH] LDAP-291: LdapTemplate correctly throws EmptyResultDataAccessException --- .../ldap/core/LdapTemplate.java | 50 ++++++++++++++++--- .../ldap/core/LdapTemplateTest.java | 50 ++++++++++++++++++- 2 files changed, 92 insertions(+), 8 deletions(-) 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 3cefcee2..9b1f6c96 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java @@ -1428,10 +1428,10 @@ public class LdapTemplate implements LdapOperations, InitializingBean { password, getDefaultSearchControls(defaultSearchScope, RETURN_OBJ_FLAG, null), callback, - errorCallback); + errorCallback).isSuccess(); } - private boolean authenticate(Name base, + private AuthenticationStatus authenticate(Name base, String filter, String password, SearchControls searchControls, @@ -1442,7 +1442,7 @@ public class LdapTemplate implements LdapOperations, InitializingBean { if (result.size() == 0) { String msg = "No results found for search, base: '" + base + "'; filter: '" + filter + "'."; LOG.info(msg); - return false; + return AuthenticationStatus.EMPTYRESULT; } else if (result.size() > 1) { String msg = "base: '" + base + "'; filter: '" + filter + "'."; throw new IncorrectResultSizeDataAccessException(msg, 1, result.size()); @@ -1458,12 +1458,12 @@ public class LdapTemplate implements LdapOperations, InitializingBean { return null; } }, ctx); - return true; + return AuthenticationStatus.SUCCESS; } catch (Exception e) { LOG.debug("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteName() + "'", e); errorCallback.execute(e); - return false; + return AuthenticationStatus.UNDEFINED_FAILURE; } } @@ -1478,7 +1478,7 @@ public class LdapTemplate implements LdapOperations, InitializingBean { CollectingAuthenticationErrorCallback errorCallback = new CollectingAuthenticationErrorCallback(); - boolean succeeded = authenticate(query.base(), + AuthenticationStatus authenticationStatus = authenticate(query.base(), query.filter().encode(), password, searchControls, @@ -1493,7 +1493,9 @@ public class LdapTemplate implements LdapOperations, InitializingBean { } else { throw new UncategorizedLdapException(error); } - } else if(!succeeded) { + } else if(AuthenticationStatus.EMPTYRESULT == authenticationStatus) { + throw new EmptyResultDataAccessException(1); + } else if(!authenticationStatus.isSuccess()) { throw new AuthenticationException(); } @@ -1871,4 +1873,38 @@ public class LdapTemplate implements LdapOperations, InitializingBean { return result.get(0); } + + /** + * The status of an authentication attempt. + * + * @author Rob Winch + */ + private enum AuthenticationStatus { + /** + * Authentication was successful + */ + SUCCESS(true), + /** + * The user was not found + */ + EMPTYRESULT(false), + /** + * Authentication failed for other reason + */ + UNDEFINED_FAILURE(false); + + private boolean success; + + AuthenticationStatus(boolean success) { + this.success = success; + } + + /** + * Return true if the authentication attempt was successful + * @return + */ + public boolean isSuccess() { + return success; + } + } } diff --git a/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java b/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java index dded783a..386f4308 100644 --- a/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java +++ b/core/src/test/java/org/springframework/ldap/core/LdapTemplateTest.java @@ -30,6 +30,8 @@ import org.springframework.ldap.PartialResultException; import org.springframework.ldap.UncategorizedLdapException; import org.springframework.ldap.filter.EqualsFilter; import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.ldap.query.LdapQueryBuilder; import org.springframework.ldap.support.LdapUtils; import javax.naming.Binding; @@ -44,6 +46,7 @@ import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapName; + import java.util.List; import static org.junit.Assert.assertEquals; @@ -103,7 +106,10 @@ public class LdapTemplateTest { private AuthenticatedLdapEntryContextCallback entryContextCallbackMock; private ObjectDirectoryMapper odmMock; - @Before + private LdapQuery query; + private AuthenticatedLdapEntryContextMapper authContextMapperMock; + + @Before public void setUp() throws Exception { // Setup ContextSource mock @@ -125,6 +131,8 @@ public class LdapTemplateTest { authenticatedContextMock = mock(DirContext.class); entryContextCallbackMock = mock(AuthenticatedLdapEntryContextCallback.class); odmMock = mock(ObjectDirectoryMapper.class); + query = LdapQueryBuilder.query().base("ou=spring").filter("ou=user"); + authContextMapperMock = mock(AuthenticatedLdapEntryContextMapper.class); tested = new LdapTemplate(contextSourceMock); tested.setObjectDirectoryMapper(odmMock); @@ -1714,6 +1722,46 @@ public class LdapTemplateTest { assertFalse(result); } + @Test + @SuppressWarnings("unchecked") + public void testAuthenticateQueryPasswordMapperWhenNoUserWasFoundShouldThrowEmptyResult() throws Exception { + + when(contextSourceMock.getReadOnlyContext()).thenReturn(dirContextMock); + + when(dirContextMock.search( + any(Name.class), + any(String.class), + any(SearchControls.class))).thenReturn(namingEnumerationMock); + + when(namingEnumerationMock.hasMore()).thenReturn(false); + + try { + tested.authenticate(query, "", authContextMapperMock); + fail("Expected Exception"); + }catch(EmptyResultDataAccessException success) {} + verify(dirContextMock).close(); + } + + @Test + @SuppressWarnings("unchecked") + public void testAuthenticateQueryPasswordWhenNoUserWasFoundShouldThrowEmptyResult() throws Exception { + + when(contextSourceMock.getReadOnlyContext()).thenReturn(dirContextMock); + + when(dirContextMock.search( + any(Name.class), + any(String.class), + any(SearchControls.class))).thenReturn(namingEnumerationMock); + + when(namingEnumerationMock.hasMore()).thenReturn(false); + + try { + tested.authenticate(query, ""); + fail("Expected Exception"); + }catch(EmptyResultDataAccessException success) {} + verify(dirContextMock).close(); + } + @Test public void testAuthenticateWithFailedAuthenticationShouldFail() throws Exception { when(contextSourceMock.getReadOnlyContext()).thenReturn(dirContextMock);