diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java index 89eba6015c..0c1c6dc12f 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java @@ -12,6 +12,7 @@ */ package org.springframework.security.ldap.authentication.ad; +import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.support.LdapUtils; @@ -24,6 +25,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.ldap.SpringSecurityLdapTemplate; import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider; import org.springframework.util.Assert; @@ -266,6 +268,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda return (BadCredentialsException) badCredentials().initCause(cause); } + @SuppressWarnings("deprecation") private DirContextOperations searchForUser(DirContext ctx, String username) throws NamingException { SearchControls searchCtls = new SearchControls(); searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); @@ -276,8 +279,18 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal); - return SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx, searchCtls, searchRoot, searchFilter, + try { + return SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx, searchCtls, searchRoot, searchFilter, new Object[]{bindPrincipal}); + } catch (IncorrectResultSizeDataAccessException incorrectResults) { + if (incorrectResults.getActualSize() == 0) { + UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException("User " + username + " not found in directory.", username); + userNameNotFoundException.initCause(incorrectResults); + throw badCredentials(userNameNotFoundException); + } + // Search should never return multiple results if properly configured, so just rethrow + throw incorrectResults; + } } private String searchRootFromPrincipal(String bindPrincipal) { diff --git a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java index 39250b4039..99779439fc 100644 --- a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java +++ b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java @@ -16,12 +16,14 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.ContextFactory; +import org.apache.directory.shared.ldap.util.EmptyEnumeration; import org.hamcrest.BaseMatcher; import org.hamcrest.CoreMatchers; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.*; import org.junit.rules.ExpectedException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.DistinguishedName; import org.springframework.security.authentication.AccountExpiredException; @@ -32,6 +34,7 @@ import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import javax.naming.AuthenticationException; import javax.naming.CommunicationException; @@ -125,6 +128,37 @@ public class ActiveDirectoryLdapAuthenticationProviderTests { provider.authenticate(joe); } + // SEC-2017 + @Test(expected = BadCredentialsException.class) + public void noUserSearchCausesUsernameNotFound() throws Exception { + DirContext ctx = mock(DirContext.class); + when(ctx.getNameInNamespace()).thenReturn(""); + when(ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class))) + .thenReturn(new EmptyEnumeration()); + + provider.contextFactory = createContextFactoryReturning(ctx); + + provider.authenticate(joe); + } + + @SuppressWarnings("unchecked") + @Test(expected = IncorrectResultSizeDataAccessException.class) + public void duplicateUserSearchCausesError() throws Exception { + DirContext ctx = mock(DirContext.class); + when(ctx.getNameInNamespace()).thenReturn(""); + NamingEnumeration searchResults = mock(NamingEnumeration.class); + when(searchResults.hasMore()).thenReturn(true,true,false); + SearchResult searchResult = mock(SearchResult.class); + when(searchResult.getName()).thenReturn("ou=1","ou=2"); + when(searchResults.next()).thenReturn(searchResult); + when(ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class))) + .thenReturn(searchResults ); + + provider.contextFactory = createContextFactoryReturning(ctx); + + provider.authenticate(joe); + } + static final String msg = "[LDAP: error code 49 - 80858585: LdapErr: DSID-DECAFF0, comment: AcceptSecurityContext error, data "; @Test(expected = BadCredentialsException.class)