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 0c1c6dc12fa..5dc1541cd2a 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 @@ -95,6 +95,8 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda private final String rootDn; private final String url; private boolean convertSubErrorCodesToExceptions; + private String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))"; + private boolean searchByBindPrincipal = true; // Only used to allow tests to substitute a mock LdapContext ContextFactory contextFactory = new ContextFactory(); @@ -169,7 +171,7 @@ private DirContext bindAsUser(String username, String password) { Hashtable env = new Hashtable(); env.put(Context.SECURITY_AUTHENTICATION, "simple"); - String bindPrincipal = createBindPrincipal(username); + String bindPrincipal = searchByBindPrincipal ? createBindPrincipal(username) : username; env.put(Context.SECURITY_PRINCIPAL, bindPrincipal); env.put(Context.PROVIDER_URL, bindUrl); env.put(Context.SECURITY_CREDENTIALS, password); @@ -273,9 +275,7 @@ private DirContextOperations searchForUser(DirContext ctx, String username) thro SearchControls searchCtls = new SearchControls(); searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); - String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))"; - - final String bindPrincipal = createBindPrincipal(username); + final String bindPrincipal = searchByBindPrincipal ? createBindPrincipal(username) : username; String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal); @@ -340,6 +340,31 @@ public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToEx this.convertSubErrorCodesToExceptions = convertSubErrorCodesToExceptions; } + /** + * If this property is set to {@code true}, the bind principal ({@code user@domain}) + * is used to bind and lookup users's authorities. + * If set to {@code false} only the username is used. + * Defaults to {@code true}. + * + * @param searchByBindPrincipal + */ + public void setSearchByBindPrincipal(boolean searchByBindPrincipal) + { + this.searchByBindPrincipal = searchByBindPrincipal; + } + + /** + * Custom search filter that is used to lookup user's authorities. + * Occurrences of {0} are replaced with the username or bind principal, depending on {@link #searchByBindPrincipal}. + * The default is: {@code (&(objectClass=user)(userPrincipalName={0})) }. + * + * @param searchFilter + */ + public void setSearchFilter(String searchFilter) + { + this.searchFilter = searchFilter; + } + static class ContextFactory { DirContext createContext(Hashtable env) throws NamingException { return new InitialLdapContext(env, null); 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 99779439fc7..0647af0d2c4 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 @@ -13,6 +13,7 @@ package org.springframework.security.ldap.authentication.ad; import static org.junit.Assert.*; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; import static org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.ContextFactory; @@ -33,11 +34,9 @@ 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; +import javax.naming.Context; import javax.naming.Name; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; @@ -86,7 +85,7 @@ public void successfulAuthenticationProducesExpectedAuthorities() throws Excepti assertEquals(0, result.getAuthorities().size()); - dca.addAttributeValue("memberOf","CN=Admin,CN=Users,DC=mydomain,DC=eu"); + dca.addAttributeValue("memberOf", "CN=Admin,CN=Users,DC=mydomain,DC=eu"); sr.setAttributes(dca.getAttributes()); @@ -95,6 +94,7 @@ public void successfulAuthenticationProducesExpectedAuthorities() throws Excepti assertEquals(1, result.getAuthorities().size()); } + @Test public void nullDomainIsSupportedIfAuthenticatingWithFullUserPrincipal() throws Exception { provider = new ActiveDirectoryLdapAuthenticationProvider(null, "ldap://192.168.1.200/"); @@ -264,6 +264,43 @@ public void nonAuthenticationExceptionIsConvertedToSpringLdapException() throws provider.authenticate(joe); } + @Test + public void searchFilterIsUsedInAuthorityLookup() throws Exception { + + String customFilter = "(&(objectClass=user)(sAMAccountName={0}))"; + DirContext ctx = mock(DirContext.class); + when(ctx.getNameInNamespace()).thenReturn(""); + + DirContextAdapter dca = new DirContextAdapter(); + SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", null, dca.getAttributes()); + when(ctx.search(any(Name.class), eq(customFilter), any(Object[].class), any(SearchControls.class))) + .thenReturn(new MockNamingEnumeration(sr)) + .thenReturn(new MockNamingEnumeration(sr)); + + provider.contextFactory = createContextFactoryReturning(ctx); + provider.setSearchFilter(customFilter); + provider.authenticate(joe); + } + + @Test + public void usernameUsedInAuthorityLookupWhenSearchByBindPrincipalIsFalse() throws Exception { + + DirContext ctx = mock(DirContext.class); + when(ctx.getNameInNamespace()).thenReturn(""); + + DirContextAdapter dca = new DirContextAdapter(); + SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", null, dca.getAttributes()); + when(ctx.search(any(Name.class), any(String.class), eq(new String[]{joe.getName()}), any(SearchControls.class))) + .thenReturn(new MockNamingEnumeration(sr)) + .thenReturn(new MockNamingEnumeration(sr)); + + provider.contextFactory = createContextFactoryReturning(ctx); + provider.setSearchByBindPrincipal(false); + provider.authenticate(joe); + + assertEquals(joe.getName(), ctx.getEnvironment().get(Context.SECURITY_PRINCIPAL) ); + } + ContextFactory createContextFactoryThrowing(final NamingException e) { return new ContextFactory() { @Override @@ -277,7 +314,8 @@ DirContext createContext(Hashtable env) throws NamingException { ContextFactory createContextFactoryReturning(final DirContext ctx) { return new ContextFactory() { @Override - DirContext createContext(Hashtable env) throws NamingException { + DirContext createContext(Hashtable env) throws NamingException { + when(ctx.getEnvironment()).thenReturn( env ); return ctx; } };