Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.CommunicationException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapClient;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
import org.springframework.ldap.core.support.SingleContextSource;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.query.SearchScope;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
Expand All @@ -50,7 +54,6 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -96,6 +99,7 @@
* @author Luke Taylor
* @author Rob Winch
* @author Roman Zabaluev
* @author Andrey Litvitski
* @since 3.1
*/
public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
Expand Down Expand Up @@ -299,10 +303,23 @@ private DirContextOperations searchForUser(DirContext context, String username)
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String bindPrincipal = createBindPrincipal(username);
String searchRoot = (this.rootDn != null) ? this.rootDn : searchRootFromPrincipal(bindPrincipal);

SingleContextSource contextSource = new SingleContextSource(context);
LdapClient ldapClient = LdapClient.builder()
.contextSource(contextSource)
.defaultSearchControls(() -> searchControls)
.ignorePartialResultException(true)
.build();
try {
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context, searchControls, searchRoot,
this.searchFilter, new Object[] { bindPrincipal, username });
DirContextOperations result = ldapClient.search()
.query(LdapQueryBuilder.query()
.base(searchRoot)
.searchScope(SearchScope.SUBTREE)
.filter(this.searchFilter, bindPrincipal, username))
.toEntry();
if (result == null) {
throw new IncorrectResultSizeDataAccessException(1, 0);
}
return result;
}
catch (CommunicationException ex) {
throw badLdapConnection(ex);
Expand All @@ -316,6 +333,9 @@ private DirContextOperations searchForUser(DirContext context, String username)
UsernameNotFoundException userNameNotFoundException = UsernameNotFoundException.fromUsername(username, ex);
throw badCredentials(userNameNotFoundException);
}
catch (org.springframework.ldap.NamingException ex) {
throw badCredentials(ex);
}
}

private String searchRootFromPrincipal(String bindPrincipal) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
* @author Luke Taylor
* @author Rob Winch
* @author Gengwu Zhao
* @author Andrey Litvitski
*/
public class ActiveDirectoryLdapAuthenticationProviderTests {

Expand Down Expand Up @@ -97,7 +98,7 @@ public void customSearchFilterIsUsedForSuccessfulAuthentication() throws Excepti
String customSearchFilter = "(&(objectClass=user)(sAMAccountName={0}))";
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
given(this.ctx.search(any(Name.class), eq(customSearchFilter), any(Object[].class), any(SearchControls.class)))
given(this.ctx.search(any(Name.class), any(String.class), any(SearchControls.class)))
.willReturn(new MockNamingEnumeration(sr));
ActiveDirectoryLdapAuthenticationProvider customProvider = new ActiveDirectoryLdapAuthenticationProvider(
"mydomain.eu", "ldap://192.168.1.200/");
Expand All @@ -109,34 +110,32 @@ public void customSearchFilterIsUsedForSuccessfulAuthentication() throws Excepti

@Test
public void defaultSearchFilter() throws Exception {
final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
given(this.ctx.search(any(Name.class), eq(defaultSearchFilter), any(Object[].class), any(SearchControls.class)))
given(this.ctx.search(any(Name.class), any(String.class), any(SearchControls.class)))
.willReturn(new MockNamingEnumeration(sr));
ActiveDirectoryLdapAuthenticationProvider customProvider = new ActiveDirectoryLdapAuthenticationProvider(
"mydomain.eu", "ldap://192.168.1.200/");
customProvider.contextFactory = createContextFactoryReturning(this.ctx);
Authentication result = customProvider.authenticate(this.joe);
assertThat(result.isAuthenticated()).isTrue();
verify(this.ctx).search(any(Name.class), eq(defaultSearchFilter), any(Object[].class),
any(SearchControls.class));
verify(this.ctx).search(any(Name.class), any(String.class), any(SearchControls.class));
}

// SEC-2897,SEC-2224
@Test
public void bindPrincipalAndUsernameUsed() throws Exception {
final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
ArgumentCaptor<Object[]> captor = ArgumentCaptor.forClass(Object[].class);
final String captureValue = "(&(objectClass=user)(userPrincipalName=[email protected]))";
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
given(this.ctx.search(any(Name.class), eq(defaultSearchFilter), captor.capture(), any(SearchControls.class)))
given(this.ctx.search(any(Name.class), captor.capture(), any(SearchControls.class)))
.willReturn(new MockNamingEnumeration(sr));
ActiveDirectoryLdapAuthenticationProvider customProvider = new ActiveDirectoryLdapAuthenticationProvider(
"mydomain.eu", "ldap://192.168.1.200/");
customProvider.contextFactory = createContextFactoryReturning(this.ctx);
Authentication result = customProvider.authenticate(this.joe);
assertThat(captor.getValue()).containsExactly("[email protected]", "joe");
assertThat(captor.getValue()).isEqualTo(captureValue);
assertThat(result.isAuthenticated()).isTrue();
}

Expand All @@ -156,7 +155,7 @@ public void nullDomainIsSupportedIfAuthenticatingWithFullUserPrincipal() throws
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
given(this.ctx.search(eq(LdapNameBuilder.newInstance("DC=mydomain,DC=eu").build()), any(String.class),
any(Object[].class), any(SearchControls.class)))
any(SearchControls.class)))
.willReturn(new MockNamingEnumeration(sr));
this.provider.contextFactory = createContextFactoryReturning(this.ctx);
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(this.joe));
Expand All @@ -165,7 +164,7 @@ public void nullDomainIsSupportedIfAuthenticatingWithFullUserPrincipal() throws

@Test
public void failedUserSearchCausesBadCredentials() throws Exception {
given(this.ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class)))
given(this.ctx.search(any(Name.class), any(String.class), any(SearchControls.class)))
.willThrow(new NameNotFoundException());
this.provider.contextFactory = createContextFactoryReturning(this.ctx);
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(this.joe));
Expand All @@ -174,7 +173,7 @@ public void failedUserSearchCausesBadCredentials() throws Exception {
// SEC-2017
@Test
public void noUserSearchCausesUsernameNotFound() throws Exception {
given(this.ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class)))
given(this.ctx.search(any(Name.class), any(String.class), any(SearchControls.class)))
.willReturn(new MockNamingEnumeration(null));
this.provider.contextFactory = createContextFactoryReturning(this.ctx);
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(this.joe));
Expand All @@ -195,8 +194,7 @@ public void duplicateUserSearchCausesError() throws Exception {
SearchResult searchResult = mock(SearchResult.class);
given(searchResult.getObject()).willReturn(new DirContextAdapter("ou=1"), new DirContextAdapter("ou=2"));
given(searchResults.next()).willReturn(searchResult);
given(this.ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class)))
.willReturn(searchResults);
given(this.ctx.search(any(Name.class), any(String.class), any(SearchControls.class))).willReturn(searchResults);
this.provider.contextFactory = createContextFactoryReturning(this.ctx);
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class)
.isThrownBy(() -> this.provider.authenticate(this.joe));
Expand Down Expand Up @@ -353,7 +351,7 @@ private void checkAuthentication(String rootDn, ActiveDirectoryLdapAuthenticatio
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
@SuppressWarnings("deprecation")
Name searchBaseDn = LdapNameBuilder.newInstance(rootDn).build();
given(this.ctx.search(eq(searchBaseDn), any(String.class), any(Object[].class), any(SearchControls.class)))
given(this.ctx.search(eq(searchBaseDn), any(String.class), any(SearchControls.class)))
.willReturn(new MockNamingEnumeration(sr))
.willReturn(new MockNamingEnumeration(sr));
provider.contextFactory = createContextFactoryReturning(this.ctx);
Expand Down
Loading