1
0
mirror of synced 2026-05-22 21:33:16 +00:00

SEC-2915: Updated Java Code Formatting

This commit is contained in:
Rob Winch
2015-03-23 11:21:19 -05:00
parent 0a2e496a84
commit ae6af5d73c
1420 changed files with 92995 additions and 85097 deletions
@@ -21,21 +21,23 @@ import org.springframework.ldap.core.support.BaseLdapPathContextSource;
* @author Luke Taylor
*/
public abstract class AbstractLdapIntegrationTests {
private static DefaultSpringSecurityContextSource contextSource;
private static DefaultSpringSecurityContextSource contextSource;
@BeforeClass
public static void createContextSource() throws Exception {
int serverPort = ApacheDSServerIntegrationTests.getServerPort();
contextSource = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:" + serverPort + "/dc=springframework,dc=org");
// OpenLDAP configuration
// contextSource = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:22389/dc=springsource,dc=com");
// contextSource.setUserDn("cn=admin,dc=springsource,dc=com");
// contextSource.setPassword("password");
contextSource.afterPropertiesSet();
}
@BeforeClass
public static void createContextSource() throws Exception {
int serverPort = ApacheDSServerIntegrationTests.getServerPort();
contextSource = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:"
+ serverPort + "/dc=springframework,dc=org");
// OpenLDAP configuration
// contextSource = new
// DefaultSpringSecurityContextSource("ldap://127.0.0.1:22389/dc=springsource,dc=com");
// contextSource.setUserDn("cn=admin,dc=springsource,dc=com");
// contextSource.setPassword("password");
contextSource.afterPropertiesSet();
}
public BaseLdapPathContextSource getContextSource() {
return contextSource;
}
public BaseLdapPathContextSource getContextSource() {
return contextSource;
}
}
@@ -17,119 +17,100 @@ import org.springframework.security.ldap.userdetails.LdapUserDetailsManagerTests
* @author Luke Taylor
*/
@RunWith(Suite.class)
@Suite.SuiteClasses( {
BindAuthenticatorTests.class,
PasswordComparisonAuthenticatorTests.class,
FilterBasedLdapUserSearchTests.class,
DefaultLdapAuthoritiesPopulatorTests.class,
LdapUserDetailsManagerTests.class,
DefaultSpringSecurityContextSourceTests.class,
SpringSecurityLdapTemplateITests.class
}
)
@Suite.SuiteClasses({ BindAuthenticatorTests.class,
PasswordComparisonAuthenticatorTests.class, FilterBasedLdapUserSearchTests.class,
DefaultLdapAuthoritiesPopulatorTests.class, LdapUserDetailsManagerTests.class,
DefaultSpringSecurityContextSourceTests.class,
SpringSecurityLdapTemplateITests.class })
public final class ApacheDSServerIntegrationTests {
private static ApacheDSContainer server;
private static Integer serverPort;
private static ApacheDSContainer server;
private static Integer serverPort;
@BeforeClass
public static void startServer() throws Exception {
// OpenLDAP configuration
// contextSource = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:22389/dc=springsource,dc=com");
// contextSource.setUserDn("cn=admin,dc=springsource,dc=com");
// contextSource.setPassword("password");
server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
int port = getAvailablePort();
server.setPort(port);
server.afterPropertiesSet();
serverPort = port;
}
@BeforeClass
public static void startServer() throws Exception {
// OpenLDAP configuration
// contextSource = new
// DefaultSpringSecurityContextSource("ldap://127.0.0.1:22389/dc=springsource,dc=com");
// contextSource.setUserDn("cn=admin,dc=springsource,dc=com");
// contextSource.setPassword("password");
server = new ApacheDSContainer("dc=springframework,dc=org",
"classpath:test-server.ldif");
int port = getAvailablePort();
server.setPort(port);
server.afterPropertiesSet();
serverPort = port;
}
@AfterClass
public static void stopServer() throws Exception {
serverPort = null;
if (server != null) {
server.stop();
}
}
@AfterClass
public static void stopServer() throws Exception {
serverPort = null;
if (server != null) {
server.stop();
}
}
/**
* Main class to allow server to be started from gradle script
*/
public static void main(String[] args) throws Exception {
ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
server.afterPropertiesSet();
}
/**
* Main class to allow server to be started from gradle script
*/
public static void main(String[] args) throws Exception {
ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org",
"classpath:test-server.ldif");
server.afterPropertiesSet();
}
public static int getServerPort() {
if(serverPort == null) {
throw new IllegalStateException("The ApacheDSContainer is not currently running");
}
return serverPort;
}
/*
@After
public final void reloadServerDataIfDirty() throws Exception {
ClassPathResource ldifs = new ClassPathResource("test-server.ldif");
public static int getServerPort() {
if (serverPort == null) {
throw new IllegalStateException(
"The ApacheDSContainer is not currently running");
}
return serverPort;
}
if (!ldifs.getFile().exists()) {
throw new IllegalStateException("Ldif file not found: " + ldifs.getFile().getAbsolutePath());
}
/*
* @After public final void reloadServerDataIfDirty() throws Exception {
* ClassPathResource ldifs = new ClassPathResource("test-server.ldif");
*
* if (!ldifs.getFile().exists()) { throw new
* IllegalStateException("Ldif file not found: " + ldifs.getFile().getAbsolutePath());
* }
*
* DirContext ctx = getContextSource().getReadWriteContext();
*
* // First of all, make sure the database is empty. Name startingPoint = new
* DistinguishedName("dc=springframework,dc=org");
*
* try { clearSubContexts(ctx, startingPoint); LdifFileLoader loader = new
* LdifFileLoader(server.getService().getAdminSession(),
* ldifs.getFile().getAbsolutePath()); loader.execute(); } finally { ctx.close(); } }
*
* private void clearSubContexts(DirContext ctx, Name name) throws NamingException {
*
* NamingEnumeration<Binding> enumeration = null; try { enumeration =
* ctx.listBindings(name); while (enumeration.hasMore()) { Binding element =
* enumeration.next(); DistinguishedName childName = new
* DistinguishedName(element.getName()); childName.prepend((DistinguishedName) name);
*
* try { ctx.destroySubcontext(childName); } catch (ContextNotEmptyException e) {
* clearSubContexts(ctx, childName); ctx.destroySubcontext(childName); } } }
* catch(NameNotFoundException ignored) { } catch (NamingException e) {
* e.printStackTrace(); } finally { try { enumeration.close(); } catch (Exception
* ignored) { } } }
*/
DirContext ctx = getContextSource().getReadWriteContext();
// First of all, make sure the database is empty.
Name startingPoint = new DistinguishedName("dc=springframework,dc=org");
try {
clearSubContexts(ctx, startingPoint);
LdifFileLoader loader = new LdifFileLoader(server.getService().getAdminSession(), ldifs.getFile().getAbsolutePath());
loader.execute();
} finally {
ctx.close();
}
}
private void clearSubContexts(DirContext ctx, Name name) throws NamingException {
NamingEnumeration<Binding> enumeration = null;
try {
enumeration = ctx.listBindings(name);
while (enumeration.hasMore()) {
Binding element = enumeration.next();
DistinguishedName childName = new DistinguishedName(element.getName());
childName.prepend((DistinguishedName) name);
try {
ctx.destroySubcontext(childName);
} catch (ContextNotEmptyException e) {
clearSubContexts(ctx, childName);
ctx.destroySubcontext(childName);
}
}
} catch(NameNotFoundException ignored) {
}
catch (NamingException e) {
e.printStackTrace();
} finally {
try {
enumeration.close();
} catch (Exception ignored) {
}
}
}
*/
private static int getAvailablePort() throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(0);
return serverSocket.getLocalPort();
} finally {
if(serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {}
}
}
}
private static int getAvailablePort() throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(0);
return serverSocket.getLocalPort();
}
finally {
if (serverSocket != null) {
try {
serverSocket.close();
}
catch (IOException e) {
}
}
}
}
}
@@ -12,121 +12,137 @@ import org.junit.Test;
import org.springframework.ldap.AuthenticationException;
import org.springframework.ldap.core.support.AbstractContextSource;
/**
* @author Luke Taylor
*/
public class DefaultSpringSecurityContextSourceTests extends AbstractLdapIntegrationTests {
@Test
public void instantiationSucceedsWithExpectedProperties() {
DefaultSpringSecurityContextSource ctxSrc =
new DefaultSpringSecurityContextSource("ldap://blah:789/dc=springframework,dc=org");
assertFalse(ctxSrc.isAnonymousReadOnly());
assertTrue(ctxSrc.isPooled());
}
@Test
public void instantiationSucceedsWithExpectedProperties() {
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(
"ldap://blah:789/dc=springframework,dc=org");
assertFalse(ctxSrc.isAnonymousReadOnly());
assertTrue(ctxSrc.isPooled());
}
@Test
public void supportsSpacesInUrl() {
new DefaultSpringSecurityContextSource("ldap://myhost:10389/dc=spring%20framework,dc=org");
}
@Test
public void supportsSpacesInUrl() {
new DefaultSpringSecurityContextSource(
"ldap://myhost:10389/dc=spring%20framework,dc=org");
}
@Test
public void poolingFlagIsSetWhenAuthenticationDnMatchesManagerUserDn() throws Exception {
EnvExposingDefaultSpringSecurityContextSource ctxSrc =
new EnvExposingDefaultSpringSecurityContextSource("ldap://blah:789/dc=springframework,dc=org");
ctxSrc.setUserDn("manager");
ctxSrc.setPassword("password");
ctxSrc.afterPropertiesSet();
assertTrue(ctxSrc.getAuthenticatedEnvForTest("manager", "password").containsKey(AbstractContextSource.SUN_LDAP_POOLING_FLAG));
}
@Test
public void poolingFlagIsSetWhenAuthenticationDnMatchesManagerUserDn()
throws Exception {
EnvExposingDefaultSpringSecurityContextSource ctxSrc = new EnvExposingDefaultSpringSecurityContextSource(
"ldap://blah:789/dc=springframework,dc=org");
ctxSrc.setUserDn("manager");
ctxSrc.setPassword("password");
ctxSrc.afterPropertiesSet();
assertTrue(ctxSrc.getAuthenticatedEnvForTest("manager", "password").containsKey(
AbstractContextSource.SUN_LDAP_POOLING_FLAG));
}
@Test
public void poolingFlagIsNotSetWhenAuthenticationDnIsNotManagerUserDn() throws Exception {
EnvExposingDefaultSpringSecurityContextSource ctxSrc =
new EnvExposingDefaultSpringSecurityContextSource("ldap://blah:789/dc=springframework,dc=org");
ctxSrc.setUserDn("manager");
ctxSrc.setPassword("password");
ctxSrc.afterPropertiesSet();
assertFalse(ctxSrc.getAuthenticatedEnvForTest("user", "password").containsKey(AbstractContextSource.SUN_LDAP_POOLING_FLAG));
}
@Test
public void poolingFlagIsNotSetWhenAuthenticationDnIsNotManagerUserDn()
throws Exception {
EnvExposingDefaultSpringSecurityContextSource ctxSrc = new EnvExposingDefaultSpringSecurityContextSource(
"ldap://blah:789/dc=springframework,dc=org");
ctxSrc.setUserDn("manager");
ctxSrc.setPassword("password");
ctxSrc.afterPropertiesSet();
assertFalse(ctxSrc.getAuthenticatedEnvForTest("user", "password").containsKey(
AbstractContextSource.SUN_LDAP_POOLING_FLAG));
}
// SEC-1145. Confirms that there is no issue here with pooling.
@Test(expected=AuthenticationException.class)
public void cantBindWithWrongPasswordImmediatelyAfterSuccessfulBind() throws Exception {
DirContext ctx = null;
try {
ctx = getContextSource().getContext("uid=Bob,ou=people,dc=springframework,dc=org", "bobspassword");
} catch (Exception e) {
}
assertNotNull(ctx);
// com.sun.jndi.ldap.LdapPoolManager.showStats(System.out);
ctx.close();
// com.sun.jndi.ldap.LdapPoolManager.showStats(System.out);
// Now get it gain, with wrong password. Should fail.
ctx = getContextSource().getContext("uid=Bob,ou=people,dc=springframework,dc=org", "wrongpassword");
ctx.close();
}
// SEC-1145. Confirms that there is no issue here with pooling.
@Test(expected = AuthenticationException.class)
public void cantBindWithWrongPasswordImmediatelyAfterSuccessfulBind()
throws Exception {
DirContext ctx = null;
try {
ctx = getContextSource().getContext(
"uid=Bob,ou=people,dc=springframework,dc=org", "bobspassword");
}
catch (Exception e) {
}
assertNotNull(ctx);
// com.sun.jndi.ldap.LdapPoolManager.showStats(System.out);
ctx.close();
// com.sun.jndi.ldap.LdapPoolManager.showStats(System.out);
// Now get it gain, with wrong password. Should fail.
ctx = getContextSource().getContext(
"uid=Bob,ou=people,dc=springframework,dc=org", "wrongpassword");
ctx.close();
}
@Test
public void serverUrlWithSpacesIsSupported() throws Exception {
DefaultSpringSecurityContextSource
contextSource = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:" + ApacheDSServerIntegrationTests.getServerPort() + "/ou=space%20cadets,dc=springframework,dc=org");
contextSource.afterPropertiesSet();
contextSource.getContext("uid=space cadet,ou=space cadets,dc=springframework,dc=org", "spacecadetspassword");
}
@Test
public void serverUrlWithSpacesIsSupported() throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
"ldap://127.0.0.1:" + ApacheDSServerIntegrationTests.getServerPort()
+ "/ou=space%20cadets,dc=springframework,dc=org");
contextSource.afterPropertiesSet();
contextSource.getContext(
"uid=space cadet,ou=space cadets,dc=springframework,dc=org",
"spacecadetspassword");
}
@Test(expected=IllegalArgumentException.class)
public void instantiationFailsWithEmptyServerList() throws Exception {
List<String> serverUrls = new ArrayList<String>();
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(serverUrls, "dc=springframework,dc=org");
ctxSrc.afterPropertiesSet();
}
@Test(expected = IllegalArgumentException.class)
public void instantiationFailsWithEmptyServerList() throws Exception {
List<String> serverUrls = new ArrayList<String>();
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(
serverUrls, "dc=springframework,dc=org");
ctxSrc.afterPropertiesSet();
}
@Test
public void instantiationSuceedsWithProperServerList() throws Exception {
List<String> serverUrls = new ArrayList<String>();
serverUrls.add("ldap://foo:789");
serverUrls.add("ldap://bar:389");
serverUrls.add("ldaps://blah:636");
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(serverUrls, "dc=springframework,dc=org");
@Test
public void instantiationSuceedsWithProperServerList() throws Exception {
List<String> serverUrls = new ArrayList<String>();
serverUrls.add("ldap://foo:789");
serverUrls.add("ldap://bar:389");
serverUrls.add("ldaps://blah:636");
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(
serverUrls, "dc=springframework,dc=org");
assertFalse(ctxSrc.isAnonymousReadOnly());
assertTrue(ctxSrc.isPooled());
}
assertFalse(ctxSrc.isAnonymousReadOnly());
assertTrue(ctxSrc.isPooled());
}
// SEC-2308
@Test
public void instantiationSuceedsWithEmtpyBaseDn() throws Exception {
String baseDn = "";
List<String> serverUrls = new ArrayList<String>();
serverUrls.add("ldap://foo:789");
serverUrls.add("ldap://bar:389");
serverUrls.add("ldaps://blah:636");
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(serverUrls, baseDn);
// SEC-2308
@Test
public void instantiationSuceedsWithEmtpyBaseDn() throws Exception {
String baseDn = "";
List<String> serverUrls = new ArrayList<String>();
serverUrls.add("ldap://foo:789");
serverUrls.add("ldap://bar:389");
serverUrls.add("ldaps://blah:636");
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(
serverUrls, baseDn);
assertFalse(ctxSrc.isAnonymousReadOnly());
assertTrue(ctxSrc.isPooled());
}
assertFalse(ctxSrc.isAnonymousReadOnly());
assertTrue(ctxSrc.isPooled());
}
@Test(expected=IllegalArgumentException.class)
public void instantiationFailsWithIncorrectServerUrl() throws Exception {
List<String> serverUrls = new ArrayList<String>();
// a simple trailing slash should be ok
serverUrls.add("ldaps://blah:636/");
// this url should be rejected because the root DN goes into a separate parameter
serverUrls.add("ldap://bar:389/dc=foobar,dc=org");
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(serverUrls, "dc=springframework,dc=org");
}
@Test(expected = IllegalArgumentException.class)
public void instantiationFailsWithIncorrectServerUrl() throws Exception {
List<String> serverUrls = new ArrayList<String>();
// a simple trailing slash should be ok
serverUrls.add("ldaps://blah:636/");
// this url should be rejected because the root DN goes into a separate parameter
serverUrls.add("ldap://bar:389/dc=foobar,dc=org");
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(
serverUrls, "dc=springframework,dc=org");
}
static class EnvExposingDefaultSpringSecurityContextSource extends DefaultSpringSecurityContextSource {
public EnvExposingDefaultSpringSecurityContextSource(String providerUrl) {
super(providerUrl);
}
static class EnvExposingDefaultSpringSecurityContextSource extends
DefaultSpringSecurityContextSource {
public EnvExposingDefaultSpringSecurityContextSource(String providerUrl) {
super(providerUrl);
}
@SuppressWarnings("unchecked")
Hashtable getAuthenticatedEnvForTest(String userDn, String password) {
return getAuthenticatedEnv(userDn, password);
}
}
@SuppressWarnings("unchecked")
Hashtable getAuthenticatedEnvForTest(String userDn, String password) {
return getAuthenticatedEnv(userDn, password);
}
}
}
@@ -36,180 +36,180 @@ import org.springframework.security.crypto.codec.Utf8;
* @author Luke Taylor
*/
public class SpringSecurityLdapTemplateITests extends AbstractLdapIntegrationTests {
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private SpringSecurityLdapTemplate template;
private SpringSecurityLdapTemplate template;
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
@Before
public void setUp() throws Exception {
template = new SpringSecurityLdapTemplate(getContextSource());
}
@Test
public void compareOfCorrectValueSucceeds() {
assertTrue(template.compare("uid=bob,ou=people", "uid", "bob"));
}
@Test
public void compareOfCorrectByteValueSucceeds() {
assertTrue(template.compare("uid=bob,ou=people", "userPassword", Utf8.encode("bobspassword")));
}
@Test
public void compareOfWrongByteValueFails() {
assertFalse(template.compare("uid=bob,ou=people", "userPassword", Utf8.encode("wrongvalue")));
}
@Test
public void compareOfWrongValueFails() {
assertFalse(template.compare("uid=bob,ou=people", "uid", "wrongvalue"));
}
// @Test
// public void testNameExistsForInValidNameFails() {
// assertFalse(template.nameExists("ou=doesntexist,dc=springframework,dc=org"));
// }
//
// @Test
// public void testNameExistsForValidNameSucceeds() {
// assertTrue(template.nameExists("ou=groups,dc=springframework,dc=org"));
// }
@Test
public void namingExceptionIsTranslatedCorrectly() {
try {
template.executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext dirContext) throws NamingException {
throw new NamingException();
}
});
fail("Expected UncategorizedLdapException on NamingException");
} catch (UncategorizedLdapException expected) {}
}
@Test
public void roleSearchReturnsCorrectNumberOfRoles() {
String param = "uid=ben,ou=people,dc=springframework,dc=org";
Set<String> values = template.searchForSingleAttributeValues("ou=groups", "(member={0})", new String[] {param}, "ou");
assertEquals("Expected 3 results from search", 3, values.size());
assertTrue(values.contains("developer"));
assertTrue(values.contains("manager"));
assertTrue(values.contains("submanager"));
}
@Before
public void setUp() throws Exception {
template = new SpringSecurityLdapTemplate(getContextSource());
}
@Test
public void testMultiAttributeRetrievalWithNullAttributeNames() {
Set<Map<String, List<String>>> values =
template.searchForMultipleAttributeValues(
"ou=people",
"(uid={0})",
new String[]{"bob"},
null);
assertEquals(1, values.size());
Map<String, List<String>> record = values.iterator().next();
assertAttributeValue(record, "uid", "bob");
assertAttributeValue(record, "objectclass", "top", "person", "organizationalPerson", "inetOrgPerson");
assertAttributeValue(record, "cn", "Bob Hamilton");
assertAttributeValue(record, "sn", "Hamilton");
assertFalse(record.containsKey("userPassword"));
}
public void compareOfCorrectValueSucceeds() {
assertTrue(template.compare("uid=bob,ou=people", "uid", "bob"));
}
@Test
public void testMultiAttributeRetrievalWithZeroLengthAttributeNames() {
Set<Map<String, List<String>>> values =
template.searchForMultipleAttributeValues(
"ou=people",
"(uid={0})",
new String[]{"bob"},
new String[0]);
assertEquals(1, values.size());
Map<String, List<String>> record = values.iterator().next();
assertAttributeValue(record, "uid", "bob");
assertAttributeValue(record, "objectclass", "top", "person", "organizationalPerson", "inetOrgPerson");
assertAttributeValue(record, "cn", "Bob Hamilton");
assertAttributeValue(record, "sn", "Hamilton");
assertFalse(record.containsKey("userPassword"));
}
@Test
public void compareOfCorrectByteValueSucceeds() {
assertTrue(template.compare("uid=bob,ou=people", "userPassword",
Utf8.encode("bobspassword")));
}
@Test
public void testMultiAttributeRetrievalWithSpecifiedAttributeNames() {
Set<Map<String, List<String>>> values =
template.searchForMultipleAttributeValues(
"ou=people",
"(uid={0})",
new String[]{"bob"},
new String[]{
"uid",
"cn",
"sn"
});
assertEquals(1, values.size());
Map<String, List<String>> record = values.iterator().next();
assertAttributeValue(record, "uid", "bob");
assertAttributeValue(record, "cn", "Bob Hamilton");
assertAttributeValue(record, "sn", "Hamilton");
assertFalse(record.containsKey("userPassword"));
assertFalse(record.containsKey("objectclass"));
}
@Test
public void compareOfWrongByteValueFails() {
assertFalse(template.compare("uid=bob,ou=people", "userPassword",
Utf8.encode("wrongvalue")));
}
protected void assertAttributeValue(Map<String, List<String>> record, String attributeName, String... values) {
assertTrue(record.containsKey(attributeName));
assertEquals(values.length, record.get(attributeName).size());
for (int i = 0; i < values.length; i++) {
assertEquals(values[i], record.get(attributeName).get(i));
}
}
@Test
public void compareOfWrongValueFails() {
assertFalse(template.compare("uid=bob,ou=people", "uid", "wrongvalue"));
}
@Test
public void testRoleSearchForMissingAttributeFailsGracefully() {
String param = "uid=ben,ou=people,dc=springframework,dc=org";
// @Test
// public void testNameExistsForInValidNameFails() {
// assertFalse(template.nameExists("ou=doesntexist,dc=springframework,dc=org"));
// }
//
// @Test
// public void testNameExistsForValidNameSucceeds() {
// assertTrue(template.nameExists("ou=groups,dc=springframework,dc=org"));
// }
Set<String> values = template.searchForSingleAttributeValues("ou=groups", "(member={0})", new String[] {param}, "mail");
@Test
public void namingExceptionIsTranslatedCorrectly() {
try {
template.executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext dirContext)
throws NamingException {
throw new NamingException();
}
});
fail("Expected UncategorizedLdapException on NamingException");
}
catch (UncategorizedLdapException expected) {
}
}
assertEquals(0, values.size());
}
@Test
public void roleSearchReturnsCorrectNumberOfRoles() {
String param = "uid=ben,ou=people,dc=springframework,dc=org";
@Test
public void roleSearchWithEscapedCharacterSucceeds() throws Exception {
String param = "cn=mouse\\, jerry,ou=people,dc=springframework,dc=org";
Set<String> values = template.searchForSingleAttributeValues("ou=groups",
"(member={0})", new String[] { param }, "ou");
Set<String> values = template.searchForSingleAttributeValues("ou=groups", "(member={0})", new String[] {param}, "cn");
assertEquals("Expected 3 results from search", 3, values.size());
assertTrue(values.contains("developer"));
assertTrue(values.contains("manager"));
assertTrue(values.contains("submanager"));
}
assertEquals(1, values.size());
}
@Test
public void testMultiAttributeRetrievalWithNullAttributeNames() {
Set<Map<String, List<String>>> values = template
.searchForMultipleAttributeValues("ou=people", "(uid={0})",
new String[] { "bob" }, null);
assertEquals(1, values.size());
Map<String, List<String>> record = values.iterator().next();
assertAttributeValue(record, "uid", "bob");
assertAttributeValue(record, "objectclass", "top", "person",
"organizationalPerson", "inetOrgPerson");
assertAttributeValue(record, "cn", "Bob Hamilton");
assertAttributeValue(record, "sn", "Hamilton");
assertFalse(record.containsKey("userPassword"));
}
@Test
public void nonSpringLdapSearchCodeTestMethod() throws Exception {
java.util.Hashtable<String, String> env = new java.util.Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:" + ApacheDSServerIntegrationTests.getServerPort());
env.put(Context.SECURITY_PRINCIPAL, "");
env.put(Context.SECURITY_CREDENTIALS, "");
@Test
public void testMultiAttributeRetrievalWithZeroLengthAttributeNames() {
Set<Map<String, List<String>>> values = template
.searchForMultipleAttributeValues("ou=people", "(uid={0})",
new String[] { "bob" }, new String[0]);
assertEquals(1, values.size());
Map<String, List<String>> record = values.iterator().next();
assertAttributeValue(record, "uid", "bob");
assertAttributeValue(record, "objectclass", "top", "person",
"organizationalPerson", "inetOrgPerson");
assertAttributeValue(record, "cn", "Bob Hamilton");
assertAttributeValue(record, "sn", "Hamilton");
assertFalse(record.containsKey("userPassword"));
}
DirContext ctx = new javax.naming.directory.InitialDirContext(env);
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
controls.setReturningObjFlag(true);
controls.setReturningAttributes(null);
String param = "cn=mouse\\, jerry,ou=people,dc=springframework,dc=org";
@Test
public void testMultiAttributeRetrievalWithSpecifiedAttributeNames() {
Set<Map<String, List<String>>> values = template
.searchForMultipleAttributeValues("ou=people", "(uid={0})",
new String[] { "bob" }, new String[] { "uid", "cn", "sn" });
assertEquals(1, values.size());
Map<String, List<String>> record = values.iterator().next();
assertAttributeValue(record, "uid", "bob");
assertAttributeValue(record, "cn", "Bob Hamilton");
assertAttributeValue(record, "sn", "Hamilton");
assertFalse(record.containsKey("userPassword"));
assertFalse(record.containsKey("objectclass"));
}
javax.naming.NamingEnumeration<SearchResult> results =
ctx.search("ou=groups,dc=springframework,dc=org",
"(member={0})", new String[] {param},
controls);
protected void assertAttributeValue(Map<String, List<String>> record,
String attributeName, String... values) {
assertTrue(record.containsKey(attributeName));
assertEquals(values.length, record.get(attributeName).size());
for (int i = 0; i < values.length; i++) {
assertEquals(values[i], record.get(attributeName).get(i));
}
}
assertTrue("Expected a result", results.hasMore());
}
@Test
public void testRoleSearchForMissingAttributeFailsGracefully() {
String param = "uid=ben,ou=people,dc=springframework,dc=org";
@Test
public void searchForSingleEntryWithEscapedCharsInDnSucceeds() {
String param = "mouse, jerry";
Set<String> values = template.searchForSingleAttributeValues("ou=groups",
"(member={0})", new String[] { param }, "mail");
template.searchForSingleEntry("ou=people", "(cn={0})", new String[] {param});
}
assertEquals(0, values.size());
}
@Test
public void roleSearchWithEscapedCharacterSucceeds() throws Exception {
String param = "cn=mouse\\, jerry,ou=people,dc=springframework,dc=org";
Set<String> values = template.searchForSingleAttributeValues("ou=groups",
"(member={0})", new String[] { param }, "cn");
assertEquals(1, values.size());
}
@Test
public void nonSpringLdapSearchCodeTestMethod() throws Exception {
java.util.Hashtable<String, String> env = new java.util.Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:"
+ ApacheDSServerIntegrationTests.getServerPort());
env.put(Context.SECURITY_PRINCIPAL, "");
env.put(Context.SECURITY_CREDENTIALS, "");
DirContext ctx = new javax.naming.directory.InitialDirContext(env);
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
controls.setReturningObjFlag(true);
controls.setReturningAttributes(null);
String param = "cn=mouse\\, jerry,ou=people,dc=springframework,dc=org";
javax.naming.NamingEnumeration<SearchResult> results = ctx.search(
"ou=groups,dc=springframework,dc=org", "(member={0})",
new String[] { param }, controls);
assertTrue("Expected a result", results.hasMore());
}
@Test
public void searchForSingleEntryWithEscapedCharsInDnSucceeds() {
String param = "mouse, jerry";
template.searchForSingleEntry("ou=people", "(cn={0})", new String[] { param });
}
}
@@ -32,99 +32,115 @@ import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
* @author Luke Taylor
*/
public class BindAuthenticatorTests extends AbstractLdapIntegrationTests {
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private BindAuthenticator authenticator;
private Authentication bob;
private BindAuthenticator authenticator;
private Authentication bob;
// ~ Methods
// ========================================================================================================
//~ Methods ========================================================================================================
@Before
public void setUp() {
authenticator = new BindAuthenticator(getContextSource());
authenticator.setMessageSource(new SpringSecurityMessageSource());
bob = new UsernamePasswordAuthenticationToken("bob", "bobspassword");
@Before
public void setUp() {
authenticator = new BindAuthenticator(getContextSource());
authenticator.setMessageSource(new SpringSecurityMessageSource());
bob = new UsernamePasswordAuthenticationToken("bob", "bobspassword");
}
}
@Test(expected = BadCredentialsException.class)
public void emptyPasswordIsRejected() {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("jen", ""));
}
@Test(expected=BadCredentialsException.class)
public void emptyPasswordIsRejected() {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("jen", ""));
}
@Test
public void testAuthenticationWithCorrectPasswordSucceeds() {
authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people",
"cn={0},ou=people" });
@Test
public void testAuthenticationWithCorrectPasswordSucceeds() {
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people", "cn={0},ou=people"});
DirContextOperations user = authenticator.authenticate(bob);
assertEquals("bob", user.getStringAttribute("uid"));
authenticator.authenticate(new UsernamePasswordAuthenticationToken(
"mouse, jerry", "jerryspassword"));
}
DirContextOperations user = authenticator.authenticate(bob);
assertEquals("bob", user.getStringAttribute("uid"));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("mouse, jerry", "jerryspassword"));
}
@Test
public void testAuthenticationWithInvalidUserNameFails() {
authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });
@Test
public void testAuthenticationWithInvalidUserNameFails() {
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"});
try {
authenticator.authenticate(new UsernamePasswordAuthenticationToken(
"nonexistentsuser", "password"));
fail("Shouldn't be able to bind with invalid username");
}
catch (BadCredentialsException expected) {
}
}
try {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("nonexistentsuser", "password"));
fail("Shouldn't be able to bind with invalid username");
} catch (BadCredentialsException expected) {}
}
@Test
public void testAuthenticationWithUserSearch() throws Exception {
// DirContextAdapter ctx = new DirContextAdapter(new
// DistinguishedName("uid=bob,ou=people"));
authenticator.setUserSearch(new FilterBasedLdapUserSearch("ou=people",
"(uid={0})", getContextSource()));
authenticator.afterPropertiesSet();
authenticator.authenticate(bob);
// SEC-1444
authenticator.setUserSearch(new FilterBasedLdapUserSearch("ou=people",
"(cn={0})", getContextSource()));
authenticator.authenticate(new UsernamePasswordAuthenticationToken(
"mouse, jerry", "jerryspassword"));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("slash/guy",
"slashguyspassword"));
// SEC-1661
authenticator.setUserSearch(new FilterBasedLdapUserSearch(
"ou=\\\"quoted people\\\"", "(cn={0})", getContextSource()));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("quote\"guy",
"quoteguyspassword"));
authenticator.setUserSearch(new FilterBasedLdapUserSearch("", "(cn={0})",
getContextSource()));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("quote\"guy",
"quoteguyspassword"));
}
@Test
public void testAuthenticationWithUserSearch() throws Exception {
//DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=bob,ou=people"));
authenticator.setUserSearch(new FilterBasedLdapUserSearch("ou=people", "(uid={0})", getContextSource()));
authenticator.afterPropertiesSet();
authenticator.authenticate(bob);
// SEC-1444
authenticator.setUserSearch(new FilterBasedLdapUserSearch("ou=people", "(cn={0})", getContextSource()));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("mouse, jerry", "jerryspassword"));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("slash/guy", "slashguyspassword"));
// SEC-1661
authenticator.setUserSearch(new FilterBasedLdapUserSearch("ou=\\\"quoted people\\\"", "(cn={0})", getContextSource()));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("quote\"guy", "quoteguyspassword"));
authenticator.setUserSearch(new FilterBasedLdapUserSearch("", "(cn={0})", getContextSource()));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("quote\"guy", "quoteguyspassword"));
}
/*
@Test
public void messingWithEscapedChars() throws Exception {
Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:22389/dc=springsource,dc=com");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=admin,dc=springsource,dc=com");
env.put(Context.SECURITY_CREDENTIALS, "password");
/*
* @Test public void messingWithEscapedChars() throws Exception {
* Hashtable<String,String> env = new Hashtable<String,String>();
* env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
* env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:22389/dc=springsource,dc=com");
* env.put(Context.SECURITY_AUTHENTICATION, "simple");
* env.put(Context.SECURITY_PRINCIPAL, "cn=admin,dc=springsource,dc=com");
* env.put(Context.SECURITY_CREDENTIALS, "password");
*
* InitialDirContext idc = new InitialDirContext(env); SearchControls searchControls =
* new SearchControls(); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
* DistinguishedName baseDn = new DistinguishedName("ou=\\\"quoted people\\\"");
* NamingEnumeration<SearchResult> matches = idc.search(baseDn, "(cn=*)", new Object[]
* {"quoteguy"}, searchControls);
*
* while(matches.hasMore()) { SearchResult match = matches.next(); DistinguishedName
* dn = new DistinguishedName(match.getName()); System.out.println("**** Match: " +
* match.getName() + " ***** " + dn);
*
* } }
*/
@Test
public void testAuthenticationWithWrongPasswordFails() {
authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });
InitialDirContext idc = new InitialDirContext(env);
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
DistinguishedName baseDn = new DistinguishedName("ou=\\\"quoted people\\\"");
NamingEnumeration<SearchResult> matches = idc.search(baseDn, "(cn=*)", new Object[] {"quoteguy"}, searchControls);
try {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("bob",
"wrongpassword"));
fail("Shouldn't be able to bind with wrong password");
}
catch (BadCredentialsException expected) {
}
}
while(matches.hasMore()) {
SearchResult match = matches.next();
DistinguishedName dn = new DistinguishedName(match.getName());
System.out.println("**** Match: " + match.getName() + " ***** " + dn);
}
}
*/
@Test
public void testAuthenticationWithWrongPasswordFails() {
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"});
try {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("bob", "wrongpassword"));
fail("Shouldn't be able to bind with wrong password");
} catch (BadCredentialsException expected) {}
}
@Test
public void testUserDnPatternReturnsCorrectDn() {
authenticator.setUserDnPatterns(new String[] {"cn={0},ou=people"});
assertEquals("cn=Joe,ou=people", authenticator.getUserDns("Joe").get(0));
}
@Test
public void testUserDnPatternReturnsCorrectDn() {
authenticator.setUserDnPatterns(new String[] { "cn={0},ou=people" });
assertEquals("cn=Joe,ou=people", authenticator.getUserDns("Joe").get(0));
}
}
@@ -15,7 +15,6 @@
package org.springframework.security.ldap.authentication;
import org.junit.*;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -26,7 +25,6 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
@@ -38,107 +36,120 @@ import static org.junit.Assert.*;
* @author Luke Taylor
*/
public class PasswordComparisonAuthenticatorTests extends AbstractLdapIntegrationTests {
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private PasswordComparisonAuthenticator authenticator;
private Authentication bob;
private Authentication ben;
private PasswordComparisonAuthenticator authenticator;
private Authentication bob;
private Authentication ben;
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
@Before
public void setUp() throws Exception {
authenticator = new PasswordComparisonAuthenticator(getContextSource());
authenticator.setPasswordEncoder(new PlaintextPasswordEncoder());
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"});
bob = new UsernamePasswordAuthenticationToken("bob", "bobspassword");
ben = new UsernamePasswordAuthenticationToken("ben", "benspassword");
}
@Before
public void setUp() throws Exception {
authenticator = new PasswordComparisonAuthenticator(getContextSource());
authenticator.setPasswordEncoder(new PlaintextPasswordEncoder());
authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });
bob = new UsernamePasswordAuthenticationToken("bob", "bobspassword");
ben = new UsernamePasswordAuthenticationToken("ben", "benspassword");
}
@Test
public void testAllAttributesAreRetrievedByDefault() {
DirContextAdapter user = (DirContextAdapter) authenticator.authenticate(bob);
//System.out.println(user.getAttributes().toString());
assertEquals("User should have 5 attributes", 5, user.getAttributes().size());
}
@Test
public void testAllAttributesAreRetrievedByDefault() {
DirContextAdapter user = (DirContextAdapter) authenticator.authenticate(bob);
// System.out.println(user.getAttributes().toString());
assertEquals("User should have 5 attributes", 5, user.getAttributes().size());
}
@Test
public void testFailedSearchGivesUserNotFoundException() throws Exception {
authenticator = new PasswordComparisonAuthenticator(getContextSource());
assertTrue("User DN matches shouldn't be available", authenticator.getUserDns("Bob").isEmpty());
authenticator.setUserSearch(new MockUserSearch(null));
authenticator.afterPropertiesSet();
@Test
public void testFailedSearchGivesUserNotFoundException() throws Exception {
authenticator = new PasswordComparisonAuthenticator(getContextSource());
assertTrue("User DN matches shouldn't be available",
authenticator.getUserDns("Bob").isEmpty());
authenticator.setUserSearch(new MockUserSearch(null));
authenticator.afterPropertiesSet();
try {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("Joe", "pass"));
fail("Expected exception on failed user search");
} catch (UsernameNotFoundException expected) {}
}
try {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("Joe",
"pass"));
fail("Expected exception on failed user search");
}
catch (UsernameNotFoundException expected) {
}
}
@Test(expected = BadCredentialsException.class)
public void testLdapPasswordCompareFailsWithWrongPassword() {
// Don't retrieve the password
authenticator.setUserAttributes(new String[] {"uid", "cn", "sn"});
authenticator.authenticate(new UsernamePasswordAuthenticationToken("bob", "wrongpass"));
}
@Test(expected = BadCredentialsException.class)
public void testLdapPasswordCompareFailsWithWrongPassword() {
// Don't retrieve the password
authenticator.setUserAttributes(new String[] { "uid", "cn", "sn" });
authenticator.authenticate(new UsernamePasswordAuthenticationToken("bob",
"wrongpass"));
}
@Test
public void testMultipleDnPatternsWorkOk() {
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=nonexistent", "uid={0},ou=people"});
authenticator.authenticate(bob);
}
@Test
public void testMultipleDnPatternsWorkOk() {
authenticator.setUserDnPatterns(new String[] { "uid={0},ou=nonexistent",
"uid={0},ou=people" });
authenticator.authenticate(bob);
}
@Test
public void testOnlySpecifiedAttributesAreRetrieved() throws Exception {
authenticator.setUserAttributes(new String[] {"uid", "userPassword"});
@Test
public void testOnlySpecifiedAttributesAreRetrieved() throws Exception {
authenticator.setUserAttributes(new String[] { "uid", "userPassword" });
DirContextAdapter user = (DirContextAdapter) authenticator.authenticate(bob);
assertEquals("Should have retrieved 2 attribute (uid, userPassword)", 2, user.getAttributes().size());
}
DirContextAdapter user = (DirContextAdapter) authenticator.authenticate(bob);
assertEquals("Should have retrieved 2 attribute (uid, userPassword)", 2, user
.getAttributes().size());
}
@Test
public void testLdapCompareSucceedsWithCorrectPassword() {
// Don't retrieve the password
authenticator.setUserAttributes(new String[] {"uid"});
authenticator.authenticate(bob);
}
@Test
public void testLdapCompareSucceedsWithCorrectPassword() {
// Don't retrieve the password
authenticator.setUserAttributes(new String[] { "uid" });
authenticator.authenticate(bob);
}
@Test
public void testLdapCompareSucceedsWithShaEncodedPassword() {
// Don't retrieve the password
authenticator.setUserAttributes(new String[] {"uid"});
authenticator.setPasswordEncoder(new LdapShaPasswordEncoder());
authenticator.authenticate(ben);
}
@Test
public void testLdapCompareSucceedsWithShaEncodedPassword() {
// Don't retrieve the password
authenticator.setUserAttributes(new String[] { "uid" });
authenticator.setPasswordEncoder(new LdapShaPasswordEncoder());
authenticator.authenticate(ben);
}
@Test(expected = IllegalArgumentException.class)
public void testPasswordEncoderCantBeNull() {
authenticator.setPasswordEncoder((PasswordEncoder)null);
}
@Test(expected = IllegalArgumentException.class)
public void testPasswordEncoderCantBeNull() {
authenticator.setPasswordEncoder((PasswordEncoder) null);
}
@Test
public void testUseOfDifferentPasswordAttributeSucceeds() {
authenticator.setPasswordAttributeName("uid");
authenticator.authenticate(new UsernamePasswordAuthenticationToken("bob", "bob"));
}
@Test
public void testUseOfDifferentPasswordAttributeSucceeds() {
authenticator.setPasswordAttributeName("uid");
authenticator.authenticate(new UsernamePasswordAuthenticationToken("bob", "bob"));
}
@Test
public void testLdapCompareWithDifferentPasswordAttributeSucceeds() {
authenticator.setUserAttributes(new String[] {"uid"});
authenticator.setPasswordAttributeName("cn");
authenticator.authenticate(new UsernamePasswordAuthenticationToken("ben", "Ben Alex"));
}
@Test
public void testLdapCompareWithDifferentPasswordAttributeSucceeds() {
authenticator.setUserAttributes(new String[] { "uid" });
authenticator.setPasswordAttributeName("cn");
authenticator.authenticate(new UsernamePasswordAuthenticationToken("ben",
"Ben Alex"));
}
@Test
public void testWithUserSearch() {
authenticator = new PasswordComparisonAuthenticator(getContextSource());
authenticator.setPasswordEncoder(new PlaintextPasswordEncoder());
assertTrue("User DN matches shouldn't be available", authenticator.getUserDns("Bob").isEmpty());
@Test
public void testWithUserSearch() {
authenticator = new PasswordComparisonAuthenticator(getContextSource());
authenticator.setPasswordEncoder(new PlaintextPasswordEncoder());
assertTrue("User DN matches shouldn't be available",
authenticator.getUserDns("Bob").isEmpty());
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=Bob,ou=people"));
ctx.setAttributeValue("userPassword", "bobspassword");
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName(
"uid=Bob,ou=people"));
ctx.setAttributeValue("userPassword", "bobspassword");
authenticator.setUserSearch(new MockUserSearch(ctx));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("shouldntbeused", "bobspassword"));
}
authenticator.setUserSearch(new MockUserSearch(ctx));
authenticator.authenticate(new UsernamePasswordAuthenticationToken(
"shouldntbeused", "bobspassword"));
}
}
@@ -32,70 +32,78 @@ import org.springframework.security.ldap.AbstractLdapIntegrationTests;
*/
public class FilterBasedLdapUserSearchTests extends AbstractLdapIntegrationTests {
@Test
public void basicSearchSucceeds() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", getContextSource());
locator.setSearchSubtree(false);
locator.setSearchTimeLimit(0);
locator.setDerefLinkFlag(false);
@Test
public void basicSearchSucceeds() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people",
"(uid={0})", getContextSource());
locator.setSearchSubtree(false);
locator.setSearchTimeLimit(0);
locator.setDerefLinkFlag(false);
DirContextOperations bob = locator.searchForUser("bob");
assertEquals("bob", bob.getStringAttribute("uid"));
DirContextOperations bob = locator.searchForUser("bob");
assertEquals("bob", bob.getStringAttribute("uid"));
assertEquals(new LdapName("uid=bob,ou=people"), bob.getDn());
}
assertEquals(new LdapName("uid=bob,ou=people"), bob.getDn());
}
@Test
public void searchForNameWithCommaSucceeds() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", getContextSource());
locator.setSearchSubtree(false);
@Test
public void searchForNameWithCommaSucceeds() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people",
"(uid={0})", getContextSource());
locator.setSearchSubtree(false);
DirContextOperations jerry = locator.searchForUser("jerry");
assertEquals("jerry", jerry.getStringAttribute("uid"));
DirContextOperations jerry = locator.searchForUser("jerry");
assertEquals("jerry", jerry.getStringAttribute("uid"));
assertEquals(new LdapName("cn=mouse\\, jerry,ou=people"), jerry.getDn());
}
assertEquals(new LdapName("cn=mouse\\, jerry,ou=people"), jerry.getDn());
}
// Try some funny business with filters.
@Test
public void extraFilterPartToExcludeBob() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people",
"(&(cn=*)(!(|(uid={0})(uid=rod)(uid=jerry)(uid=slashguy)(uid=javadude)(uid=groovydude)(uid=closuredude)(uid=scaladude))))", getContextSource());
// Try some funny business with filters.
@Test
public void extraFilterPartToExcludeBob() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch(
"ou=people",
"(&(cn=*)(!(|(uid={0})(uid=rod)(uid=jerry)(uid=slashguy)(uid=javadude)(uid=groovydude)(uid=closuredude)(uid=scaladude))))",
getContextSource());
// Search for bob, get back ben...
DirContextOperations ben = locator.searchForUser("bob");
assertEquals("Ben Alex", ben.getStringAttribute("cn"));
}
// Search for bob, get back ben...
DirContextOperations ben = locator.searchForUser("bob");
assertEquals("Ben Alex", ben.getStringAttribute("cn"));
}
@Test(expected=IncorrectResultSizeDataAccessException.class)
public void searchFailsOnMultipleMatches() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(cn=*)", getContextSource());
locator.searchForUser("Ignored");
}
@Test(expected = IncorrectResultSizeDataAccessException.class)
public void searchFailsOnMultipleMatches() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people",
"(cn=*)", getContextSource());
locator.searchForUser("Ignored");
}
@Test(expected=UsernameNotFoundException.class)
public void searchForInvalidUserFails() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", getContextSource());
locator.searchForUser("Joe");
}
@Test(expected = UsernameNotFoundException.class)
public void searchForInvalidUserFails() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people",
"(uid={0})", getContextSource());
locator.searchForUser("Joe");
}
@Test
public void subTreeSearchSucceeds() throws Exception {
// Don't set the searchBase, so search from the root.
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("", "(cn={0})", getContextSource());
locator.setSearchSubtree(true);
@Test
public void subTreeSearchSucceeds() throws Exception {
// Don't set the searchBase, so search from the root.
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("", "(cn={0})",
getContextSource());
locator.setSearchSubtree(true);
DirContextOperations ben = locator.searchForUser("Ben Alex");
assertEquals("ben", ben.getStringAttribute("uid"));
DirContextOperations ben = locator.searchForUser("Ben Alex");
assertEquals("ben", ben.getStringAttribute("uid"));
assertEquals(new LdapName("uid=ben,ou=people"), ben.getDn());
}
assertEquals(new LdapName("uid=ben,ou=people"), ben.getDn());
}
@Test
public void searchWithDifferentSearchBaseIsSuccessful() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=otherpeople", "(cn={0})", getContextSource());
DirContextOperations joe = locator.searchForUser("Joe Smeth");
assertEquals("Joe Smeth", joe.getStringAttribute("cn"));
}
@Test
public void searchWithDifferentSearchBaseIsSuccessful() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch(
"ou=otherpeople", "(cn={0})", getContextSource());
DirContextOperations joe = locator.searchForUser("Joe Smeth");
assertEquals("Joe Smeth", joe.getStringAttribute("cn"));
}
}
@@ -33,65 +33,82 @@ import org.junit.Test;
*/
public class ApacheDSContainerTests {
// SEC-2162
@Test
public void failsToStartThrowsException() throws Exception {
ApacheDSContainer server1 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
ApacheDSContainer server2 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:missing.ldif");
List<Integer> ports = getDefaultPorts(1);
server1.setPort(ports.get(0));
server2.setPort(ports.get(0));
try {
server1.afterPropertiesSet();
try {
server2.afterPropertiesSet();
fail("Expected Exception");
} catch(RuntimeException success) {}
} finally {
try {
server1.destroy();
}catch(Throwable t) {}
try {
server2.destroy();
}catch(Throwable t) {}
}
}
// SEC-2162
@Test
public void failsToStartThrowsException() throws Exception {
ApacheDSContainer server1 = new ApacheDSContainer("dc=springframework,dc=org",
"classpath:test-server.ldif");
ApacheDSContainer server2 = new ApacheDSContainer("dc=springframework,dc=org",
"classpath:missing.ldif");
List<Integer> ports = getDefaultPorts(1);
server1.setPort(ports.get(0));
server2.setPort(ports.get(0));
try {
server1.afterPropertiesSet();
try {
server2.afterPropertiesSet();
fail("Expected Exception");
}
catch (RuntimeException success) {
}
}
finally {
try {
server1.destroy();
}
catch (Throwable t) {
}
try {
server2.destroy();
}
catch (Throwable t) {
}
}
}
// SEC-2161
@Test
public void multipleInstancesSimultanciously() throws Exception {
ApacheDSContainer server1 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
ApacheDSContainer server2 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
List<Integer> ports = getDefaultPorts(2);
server1.setPort(ports.get(0));
server2.setPort(ports.get(1));
try {
server1.afterPropertiesSet();
server2.afterPropertiesSet();
} finally {
try {
server1.destroy();
}catch(Throwable t) {}
try {
server2.destroy();
}catch(Throwable t) {}
}
}
// SEC-2161
@Test
public void multipleInstancesSimultanciously() throws Exception {
ApacheDSContainer server1 = new ApacheDSContainer("dc=springframework,dc=org",
"classpath:test-server.ldif");
ApacheDSContainer server2 = new ApacheDSContainer("dc=springframework,dc=org",
"classpath:test-server.ldif");
List<Integer> ports = getDefaultPorts(2);
server1.setPort(ports.get(0));
server2.setPort(ports.get(1));
try {
server1.afterPropertiesSet();
server2.afterPropertiesSet();
}
finally {
try {
server1.destroy();
}
catch (Throwable t) {
}
try {
server2.destroy();
}
catch (Throwable t) {
}
}
}
private List<Integer> getDefaultPorts(int count) throws IOException {
List<ServerSocket> connections = new ArrayList<ServerSocket>();
List<Integer> availablePorts = new ArrayList<Integer>(count);
try {
for(int i=0;i<count;i++) {
ServerSocket socket = new ServerSocket(0);
connections.add(socket);
availablePorts.add(socket.getLocalPort());
}
return availablePorts;
} finally {
for(ServerSocket conn : connections) {
conn.close();
}
}
}
private List<Integer> getDefaultPorts(int count) throws IOException {
List<ServerSocket> connections = new ArrayList<ServerSocket>();
List<Integer> availablePorts = new ArrayList<Integer>(count);
try {
for (int i = 0; i < count; i++) {
ServerSocket socket = new ServerSocket(0);
connections.add(socket);
availablePorts.add(socket.getLocalPort());
}
return availablePorts;
}
finally {
for (ServerSocket conn : connections) {
conn.close();
}
}
}
}
@@ -15,7 +15,6 @@
package org.springframework.security.ldap.userdetails;
import static org.junit.Assert.*;
import org.junit.*;
@@ -28,134 +27,149 @@ import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import java.util.*;
/**
*
* @author Luke Taylor
*/
@SuppressWarnings({"deprecation"})
@SuppressWarnings({ "deprecation" })
public class DefaultLdapAuthoritiesPopulatorTests extends AbstractLdapIntegrationTests {
private DefaultLdapAuthoritiesPopulator populator;
//~ Methods ========================================================================================================
private DefaultLdapAuthoritiesPopulator populator;
@Before
public void setUp() throws Exception {
populator = new DefaultLdapAuthoritiesPopulator(getContextSource(), "ou=groups");
populator.setIgnorePartialResultException(false);
}
// ~ Methods
// ========================================================================================================
@Test
public void defaultRoleIsAssignedWhenSet() {
populator.setDefaultRole("ROLE_USER");
assertSame(getContextSource(), populator.getContextSource());
@Before
public void setUp() throws Exception {
populator = new DefaultLdapAuthoritiesPopulator(getContextSource(), "ou=groups");
populator.setIgnorePartialResultException(false);
}
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("cn=notfound"));
@Test
public void defaultRoleIsAssignedWhenSet() {
populator.setDefaultRole("ROLE_USER");
assertSame(getContextSource(), populator.getContextSource());
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "notfound");
assertEquals(1, authorities.size());
assertTrue(AuthorityUtils.authorityListToSet(authorities).contains("ROLE_USER"));
}
DirContextAdapter ctx = new DirContextAdapter(
new DistinguishedName("cn=notfound"));
@Test
public void nullSearchBaseIsAccepted() throws Exception {
populator = new DefaultLdapAuthoritiesPopulator(getContextSource(), null);
populator.setDefaultRole("ROLE_USER");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,
"notfound");
assertEquals(1, authorities.size());
assertTrue(AuthorityUtils.authorityListToSet(authorities).contains("ROLE_USER"));
}
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(
new DirContextAdapter(new DistinguishedName("cn=notused")), "notused");
assertEquals(1, authorities.size());
assertTrue(AuthorityUtils.authorityListToSet(authorities).contains("ROLE_USER"));
}
@Test
public void nullSearchBaseIsAccepted() throws Exception {
populator = new DefaultLdapAuthoritiesPopulator(getContextSource(), null);
populator.setDefaultRole("ROLE_USER");
@Test
public void groupSearchReturnsExpectedRoles() {
populator.setRolePrefix("ROLE_");
populator.setGroupRoleAttribute("ou");
populator.setSearchSubtree(true);
populator.setSearchSubtree(false);
populator.setConvertToUpperCase(true);
populator.setGroupSearchFilter("(member={0})");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(
new DirContextAdapter(new DistinguishedName("cn=notused")), "notused");
assertEquals(1, authorities.size());
assertTrue(AuthorityUtils.authorityListToSet(authorities).contains("ROLE_USER"));
}
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
@Test
public void groupSearchReturnsExpectedRoles() {
populator.setRolePrefix("ROLE_");
populator.setGroupRoleAttribute("ou");
populator.setSearchSubtree(true);
populator.setSearchSubtree(false);
populator.setConvertToUpperCase(true);
populator.setGroupSearchFilter("(member={0})");
Set<String> authorities = AuthorityUtils.authorityListToSet(populator.getGrantedAuthorities(ctx, "ben"));
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName(
"uid=ben,ou=people,dc=springframework,dc=org"));
assertEquals("Should have 2 roles", 2, authorities.size());
Set<String> authorities = AuthorityUtils.authorityListToSet(populator
.getGrantedAuthorities(ctx, "ben"));
assertTrue(authorities.contains("ROLE_DEVELOPER"));
assertTrue(authorities.contains("ROLE_MANAGER"));
}
assertEquals("Should have 2 roles", 2, authorities.size());
@Test
public void useOfUsernameParameterReturnsExpectedRoles() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
populator.setGroupSearchFilter("(ou={1})");
assertTrue(authorities.contains("ROLE_DEVELOPER"));
assertTrue(authorities.contains("ROLE_MANAGER"));
}
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
@Test
public void useOfUsernameParameterReturnsExpectedRoles() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
populator.setGroupSearchFilter("(ou={1})");
Set<String> authorities = AuthorityUtils.authorityListToSet(populator.getGrantedAuthorities(ctx, "manager"));
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName(
"uid=ben,ou=people,dc=springframework,dc=org"));
assertEquals("Should have 1 role", 1, authorities.size());
assertTrue(authorities.contains("ROLE_MANAGER"));
}
Set<String> authorities = AuthorityUtils.authorityListToSet(populator
.getGrantedAuthorities(ctx, "manager"));
@Test
public void subGroupRolesAreNotFoundByDefault() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
assertEquals("Should have 1 role", 1, authorities.size());
assertTrue(authorities.contains("ROLE_MANAGER"));
}
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
@Test
public void subGroupRolesAreNotFoundByDefault() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
Set<String> authorities = AuthorityUtils.authorityListToSet(populator.getGrantedAuthorities(ctx, "manager"));
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName(
"uid=ben,ou=people,dc=springframework,dc=org"));
assertEquals("Should have 2 roles", 2, authorities.size());
assertTrue(authorities.contains("ROLE_MANAGER"));
assertTrue(authorities.contains("ROLE_DEVELOPER"));
}
Set<String> authorities = AuthorityUtils.authorityListToSet(populator
.getGrantedAuthorities(ctx, "manager"));
@Test
public void subGroupRolesAreFoundWhenSubtreeSearchIsEnabled() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
populator.setSearchSubtree(true);
assertEquals("Should have 2 roles", 2, authorities.size());
assertTrue(authorities.contains("ROLE_MANAGER"));
assertTrue(authorities.contains("ROLE_DEVELOPER"));
}
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
@Test
public void subGroupRolesAreFoundWhenSubtreeSearchIsEnabled() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
populator.setSearchSubtree(true);
Set<String> authorities = AuthorityUtils.authorityListToSet(populator.getGrantedAuthorities(ctx, "manager"));
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName(
"uid=ben,ou=people,dc=springframework,dc=org"));
assertEquals("Should have 3 roles", 3, authorities.size());
assertTrue(authorities.contains("ROLE_MANAGER"));
assertTrue(authorities.contains("ROLE_SUBMANAGER"));
assertTrue(authorities.contains("ROLE_DEVELOPER"));
}
Set<String> authorities = AuthorityUtils.authorityListToSet(populator
.getGrantedAuthorities(ctx, "manager"));
@Test
public void extraRolesAreAdded() throws Exception {
populator = new DefaultLdapAuthoritiesPopulator(getContextSource(), null) {
@Override
protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, String username) {
return new HashSet<GrantedAuthority>(AuthorityUtils.createAuthorityList("ROLE_EXTRA"));
}
};
assertEquals("Should have 3 roles", 3, authorities.size());
assertTrue(authorities.contains("ROLE_MANAGER"));
assertTrue(authorities.contains("ROLE_SUBMANAGER"));
assertTrue(authorities.contains("ROLE_DEVELOPER"));
}
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(
new DirContextAdapter(new DistinguishedName("cn=notused")), "notused");
assertEquals(1, authorities.size());
assertTrue(AuthorityUtils.authorityListToSet(authorities).contains("ROLE_EXTRA"));
}
@Test
public void extraRolesAreAdded() throws Exception {
populator = new DefaultLdapAuthoritiesPopulator(getContextSource(), null) {
@Override
protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user,
String username) {
return new HashSet<GrantedAuthority>(
AuthorityUtils.createAuthorityList("ROLE_EXTRA"));
}
};
@Test
public void userDnWithEscapedCharacterParameterReturnsExpectedRoles() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
populator.setGroupSearchFilter("(member={0})");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(
new DirContextAdapter(new DistinguishedName("cn=notused")), "notused");
assertEquals(1, authorities.size());
assertTrue(AuthorityUtils.authorityListToSet(authorities).contains("ROLE_EXTRA"));
}
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("cn=mouse\\, jerry,ou=people,dc=springframework,dc=org"));
@Test
public void userDnWithEscapedCharacterParameterReturnsExpectedRoles() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
populator.setGroupSearchFilter("(member={0})");
Set<String> authorities = AuthorityUtils.authorityListToSet(populator.getGrantedAuthorities(ctx, "notused"));
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName(
"cn=mouse\\, jerry,ou=people,dc=springframework,dc=org"));
assertEquals("Should have 1 role", 1, authorities.size());
assertTrue(authorities.contains("ROLE_MANAGER"));
}
Set<String> authorities = AuthorityUtils.authorityListToSet(populator
.getGrantedAuthorities(ctx, "notused"));
assertEquals("Should have 1 role", 1, authorities.size());
assertTrue(authorities.contains("ROLE_MANAGER"));
}
}
@@ -42,172 +42,179 @@ import org.springframework.security.ldap.userdetails.PersonContextMapper;
* @author Luke Taylor
*/
public class LdapUserDetailsManagerTests extends AbstractLdapIntegrationTests {
private static final List<GrantedAuthority> TEST_AUTHORITIES = AuthorityUtils.createAuthorityList("ROLE_CLOWNS","ROLE_ACROBATS");
private LdapUserDetailsManager mgr;
private SpringSecurityLdapTemplate template;
private static final List<GrantedAuthority> TEST_AUTHORITIES = AuthorityUtils
.createAuthorityList("ROLE_CLOWNS", "ROLE_ACROBATS");
private LdapUserDetailsManager mgr;
private SpringSecurityLdapTemplate template;
@Before
public void setUp() throws Exception {
mgr = new LdapUserDetailsManager(getContextSource());
template = new SpringSecurityLdapTemplate(getContextSource());
DirContextAdapter ctx = new DirContextAdapter();
@Before
public void setUp() throws Exception {
mgr = new LdapUserDetailsManager(getContextSource());
template = new SpringSecurityLdapTemplate(getContextSource());
DirContextAdapter ctx = new DirContextAdapter();
ctx.setAttributeValue("objectclass", "organizationalUnit");
ctx.setAttributeValue("ou", "test people");
template.bind("ou=test people", ctx, null);
ctx.setAttributeValue("objectclass", "organizationalUnit");
ctx.setAttributeValue("ou", "test people");
template.bind("ou=test people", ctx, null);
ctx.setAttributeValue("ou", "testgroups");
template.bind("ou=testgroups", ctx, null);
ctx.setAttributeValue("ou", "testgroups");
template.bind("ou=testgroups", ctx, null);
DirContextAdapter group = new DirContextAdapter();
DirContextAdapter group = new DirContextAdapter();
group.setAttributeValue("objectclass", "groupOfNames");
group.setAttributeValue("cn", "clowns");
group.setAttributeValue("member", "cn=nobody,ou=test people,dc=springframework,dc=org");
template.bind("cn=clowns,ou=testgroups", group, null);
group.setAttributeValue("objectclass", "groupOfNames");
group.setAttributeValue("cn", "clowns");
group.setAttributeValue("member",
"cn=nobody,ou=test people,dc=springframework,dc=org");
template.bind("cn=clowns,ou=testgroups", group, null);
group.setAttributeValue("cn", "acrobats");
template.bind("cn=acrobats,ou=testgroups", group, null);
group.setAttributeValue("cn", "acrobats");
template.bind("cn=acrobats,ou=testgroups", group, null);
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=test people","uid"));
mgr.setGroupSearchBase("ou=testgroups");
mgr.setGroupRoleAttributeName("cn");
mgr.setGroupMemberAttributeName("member");
mgr.setUserDetailsMapper(new PersonContextMapper());
}
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=test people", "uid"));
mgr.setGroupSearchBase("ou=testgroups");
mgr.setGroupRoleAttributeName("cn");
mgr.setGroupMemberAttributeName("member");
mgr.setUserDetailsMapper(new PersonContextMapper());
}
@After
public void onTearDown() throws Exception {
// Iterator people = template.list("ou=testpeople").iterator();
@After
public void onTearDown() throws Exception {
// Iterator people = template.list("ou=testpeople").iterator();
// DirContext rootCtx = new DirContextAdapter(new DistinguishedName(getInitialCtxFactory().getRootDn()));
//
// while(people.hasNext()) {
// template.unbind((String) people.next() + ",ou=testpeople");
// }
// DirContext rootCtx = new DirContextAdapter(new
// DistinguishedName(getInitialCtxFactory().getRootDn()));
//
// while(people.hasNext()) {
// template.unbind((String) people.next() + ",ou=testpeople");
// }
template.unbind("ou=test people",true);
template.unbind("ou=testgroups",true);
template.unbind("ou=test people", true);
template.unbind("ou=testgroups", true);
SecurityContextHolder.clearContext();
}
SecurityContextHolder.clearContext();
}
@Test
public void testLoadUserByUsernameReturnsCorrectData() {
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people","uid"));
mgr.setGroupSearchBase("ou=groups");
LdapUserDetails bob = (LdapUserDetails) mgr.loadUserByUsername("bob");
assertEquals("bob", bob.getUsername());
assertEquals("uid=bob,ou=people,dc=springframework,dc=org", bob.getDn());
assertEquals("bobspassword", bob.getPassword());
@Test
public void testLoadUserByUsernameReturnsCorrectData() {
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people", "uid"));
mgr.setGroupSearchBase("ou=groups");
LdapUserDetails bob = (LdapUserDetails) mgr.loadUserByUsername("bob");
assertEquals("bob", bob.getUsername());
assertEquals("uid=bob,ou=people,dc=springframework,dc=org", bob.getDn());
assertEquals("bobspassword", bob.getPassword());
assertEquals(1, bob.getAuthorities().size());
}
assertEquals(1, bob.getAuthorities().size());
}
@Test(expected = UsernameNotFoundException.class)
public void testLoadingInvalidUsernameThrowsUsernameNotFoundException() {
mgr.loadUserByUsername("jim");
}
@Test(expected = UsernameNotFoundException.class)
public void testLoadingInvalidUsernameThrowsUsernameNotFoundException() {
mgr.loadUserByUsername("jim");
}
@Test
public void testUserExistsReturnsTrueForValidUser() {
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people","uid"));
assertTrue(mgr.userExists("bob"));
}
@Test
public void testUserExistsReturnsTrueForValidUser() {
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people", "uid"));
assertTrue(mgr.userExists("bob"));
}
@Test
public void testUserExistsReturnsFalseForInValidUser() {
assertFalse(mgr.userExists("jim"));
}
@Test
public void testUserExistsReturnsFalseForInValidUser() {
assertFalse(mgr.userExists("jim"));
}
@Test
public void testCreateNewUserSucceeds() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setCarLicense("XXX");
p.setCn(new String[] {"Joe Smeth"});
p.setDepartmentNumber("5679");
p.setDescription("Some description");
p.setDn("whocares");
p.setEmployeeNumber("E781");
p.setInitials("J");
p.setMail("joe@smeth.com");
p.setMobile("+44776542911");
p.setOu("Joes Unit");
p.setO("Organization");
p.setRoomNumber("500X");
p.setSn("Smeth");
p.setUid("joe");
@Test
public void testCreateNewUserSucceeds() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setCarLicense("XXX");
p.setCn(new String[] { "Joe Smeth" });
p.setDepartmentNumber("5679");
p.setDescription("Some description");
p.setDn("whocares");
p.setEmployeeNumber("E781");
p.setInitials("J");
p.setMail("joe@smeth.com");
p.setMobile("+44776542911");
p.setOu("Joes Unit");
p.setO("Organization");
p.setRoomNumber("500X");
p.setSn("Smeth");
p.setUid("joe");
p.setAuthorities(TEST_AUTHORITIES);
p.setAuthorities(TEST_AUTHORITIES);
mgr.createUser(p.createUserDetails());
}
mgr.createUser(p.createUserDetails());
}
@Test
public void testDeleteUserSucceeds() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setDn("whocares");
p.setCn(new String[] {"Don Smeth"});
p.setSn("Smeth");
p.setUid("don");
p.setAuthorities(TEST_AUTHORITIES);
@Test
public void testDeleteUserSucceeds() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setDn("whocares");
p.setCn(new String[] { "Don Smeth" });
p.setSn("Smeth");
p.setUid("don");
p.setAuthorities(TEST_AUTHORITIES);
mgr.createUser(p.createUserDetails());
mgr.setUserDetailsMapper(new InetOrgPersonContextMapper());
mgr.createUser(p.createUserDetails());
mgr.setUserDetailsMapper(new InetOrgPersonContextMapper());
InetOrgPerson don = (InetOrgPerson) mgr.loadUserByUsername("don");
InetOrgPerson don = (InetOrgPerson) mgr.loadUserByUsername("don");
assertEquals(2, don.getAuthorities().size());
assertEquals(2, don.getAuthorities().size());
mgr.deleteUser("don");
mgr.deleteUser("don");
try {
mgr.loadUserByUsername("don");
fail("Expected UsernameNotFoundException after deleting user");
} catch(UsernameNotFoundException expected) {
// expected
}
try {
mgr.loadUserByUsername("don");
fail("Expected UsernameNotFoundException after deleting user");
}
catch (UsernameNotFoundException expected) {
// expected
}
// Check that no authorities are left
assertEquals(0, mgr.getUserAuthorities(mgr.usernameMapper.buildDn("don"), "don").size());
}
// Check that no authorities are left
assertEquals(0, mgr.getUserAuthorities(mgr.usernameMapper.buildDn("don"), "don")
.size());
}
@Test
public void testPasswordChangeWithCorrectOldPasswordSucceeds() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setDn("whocares");
p.setCn(new String[] {"John Yossarian"});
p.setSn("Yossarian");
p.setUid("johnyossarian");
p.setPassword("yossarianspassword");
p.setAuthorities(TEST_AUTHORITIES);
@Test
public void testPasswordChangeWithCorrectOldPasswordSucceeds() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setDn("whocares");
p.setCn(new String[] { "John Yossarian" });
p.setSn("Yossarian");
p.setUid("johnyossarian");
p.setPassword("yossarianspassword");
p.setAuthorities(TEST_AUTHORITIES);
mgr.createUser(p.createUserDetails());
mgr.createUser(p.createUserDetails());
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("johnyossarian", "yossarianspassword", TEST_AUTHORITIES));
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("johnyossarian",
"yossarianspassword", TEST_AUTHORITIES));
mgr.changePassword("yossarianspassword", "yossariansnewpassword");
mgr.changePassword("yossarianspassword", "yossariansnewpassword");
assertTrue(template.compare("uid=johnyossarian,ou=test people",
"userPassword", "yossariansnewpassword"));
}
assertTrue(template.compare("uid=johnyossarian,ou=test people", "userPassword",
"yossariansnewpassword"));
}
@Test(expected = BadCredentialsException.class)
public void testPasswordChangeWithWrongOldPasswordFails() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setDn("whocares");
p.setCn(new String[] {"John Yossarian"});
p.setSn("Yossarian");
p.setUid("johnyossarian");
p.setPassword("yossarianspassword");
p.setAuthorities(TEST_AUTHORITIES);
@Test(expected = BadCredentialsException.class)
public void testPasswordChangeWithWrongOldPasswordFails() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setDn("whocares");
p.setCn(new String[] { "John Yossarian" });
p.setSn("Yossarian");
p.setUid("johnyossarian");
p.setPassword("yossarianspassword");
p.setAuthorities(TEST_AUTHORITIES);
mgr.createUser(p.createUserDetails());
mgr.createUser(p.createUserDetails());
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("johnyossarian", "yossarianspassword", TEST_AUTHORITIES));
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("johnyossarian",
"yossarianspassword", TEST_AUTHORITIES));
mgr.changePassword("wrongpassword", "yossariansnewpassword");
}
mgr.changePassword("wrongpassword", "yossariansnewpassword");
}
}
@@ -14,7 +14,6 @@
*/
package org.springframework.security.ldap.userdetails;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ldap.core.DirContextAdapter;
@@ -32,100 +31,120 @@ import static org.junit.Assert.*;
*/
public class NestedLdapAuthoritiesPopulatorTests extends AbstractLdapIntegrationTests {
private NestedLdapAuthoritiesPopulator populator;
private LdapAuthority javaDevelopers;
private LdapAuthority groovyDevelopers;
private LdapAuthority scalaDevelopers;
private LdapAuthority closureDevelopers;
private LdapAuthority jDevelopers;
private LdapAuthority circularJavaDevelopers;
//~ Methods ========================================================================================================
private NestedLdapAuthoritiesPopulator populator;
private LdapAuthority javaDevelopers;
private LdapAuthority groovyDevelopers;
private LdapAuthority scalaDevelopers;
private LdapAuthority closureDevelopers;
private LdapAuthority jDevelopers;
private LdapAuthority circularJavaDevelopers;
@Before
public void setUp() throws Exception {
populator = new NestedLdapAuthoritiesPopulator(getContextSource(), "ou=jdeveloper");
populator.setGroupSearchFilter("(member={0})");
populator.setIgnorePartialResultException(false);
populator.setRolePrefix("");
populator.setSearchSubtree(true);
populator.setConvertToUpperCase(false);
jDevelopers = new LdapAuthority("j-developers", "cn=j-developers,ou=jdeveloper,dc=springframework,dc=org");
javaDevelopers = new LdapAuthority("java-developers", "cn=java-developers,ou=jdeveloper,dc=springframework,dc=org");
groovyDevelopers = new LdapAuthority("groovy-developers", "cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org");
scalaDevelopers = new LdapAuthority("scala-developers", "cn=scala-developers,ou=jdeveloper,dc=springframework,dc=org");
closureDevelopers = new LdapAuthority("closure-developers", "cn=closure-developers,ou=jdeveloper,dc=springframework,dc=org");
circularJavaDevelopers = new LdapAuthority("circular-java-developers", "cn=circular-java-developers,ou=jdeveloper,dc=springframework,dc=org");
}
// ~ Methods
// ========================================================================================================
@Test
public void testScalaDudeJDevelopersAuthorities() {
DirContextAdapter ctx = new DirContextAdapter("uid=scaladude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "scaladude");
assertEquals(5, authorities.size());
assertEquals(Arrays.asList(javaDevelopers, scalaDevelopers, circularJavaDevelopers, jDevelopers, groovyDevelopers), authorities);
}
@Before
public void setUp() throws Exception {
populator = new NestedLdapAuthoritiesPopulator(getContextSource(),
"ou=jdeveloper");
populator.setGroupSearchFilter("(member={0})");
populator.setIgnorePartialResultException(false);
populator.setRolePrefix("");
populator.setSearchSubtree(true);
populator.setConvertToUpperCase(false);
jDevelopers = new LdapAuthority("j-developers",
"cn=j-developers,ou=jdeveloper,dc=springframework,dc=org");
javaDevelopers = new LdapAuthority("java-developers",
"cn=java-developers,ou=jdeveloper,dc=springframework,dc=org");
groovyDevelopers = new LdapAuthority("groovy-developers",
"cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org");
scalaDevelopers = new LdapAuthority("scala-developers",
"cn=scala-developers,ou=jdeveloper,dc=springframework,dc=org");
closureDevelopers = new LdapAuthority("closure-developers",
"cn=closure-developers,ou=jdeveloper,dc=springframework,dc=org");
circularJavaDevelopers = new LdapAuthority("circular-java-developers",
"cn=circular-java-developers,ou=jdeveloper,dc=springframework,dc=org");
}
@Test
public void testJavaDudeJDevelopersAuthorities() {
DirContextAdapter ctx = new DirContextAdapter("uid=javadude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "javadude");
assertEquals(3, authorities.size());
assertEquals(Arrays.asList(javaDevelopers, circularJavaDevelopers, jDevelopers), authorities);
}
@Test
public void testScalaDudeJDevelopersAuthorities() {
DirContextAdapter ctx = new DirContextAdapter(
"uid=scaladude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,
"scaladude");
assertEquals(5, authorities.size());
assertEquals(Arrays.asList(javaDevelopers, scalaDevelopers,
circularJavaDevelopers, jDevelopers, groovyDevelopers), authorities);
}
@Test
public void testScalaDudeJDevelopersAuthoritiesWithSearchLimit() {
populator.setMaxSearchDepth(1);
DirContextAdapter ctx = new DirContextAdapter("uid=scaladude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "scaladude");
assertEquals(1, authorities.size());
assertEquals(Arrays.asList(scalaDevelopers), authorities);
}
@Test
public void testJavaDudeJDevelopersAuthorities() {
DirContextAdapter ctx = new DirContextAdapter(
"uid=javadude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,
"javadude");
assertEquals(3, authorities.size());
assertEquals(Arrays.asList(javaDevelopers, circularJavaDevelopers, jDevelopers),
authorities);
}
@Test
public void testGroovyDudeJDevelopersAuthorities() {
DirContextAdapter ctx = new DirContextAdapter("uid=groovydude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "groovydude");
assertEquals(4, authorities.size());
assertEquals(Arrays.asList(javaDevelopers, circularJavaDevelopers, jDevelopers, groovyDevelopers), authorities);
}
@Test
public void testScalaDudeJDevelopersAuthoritiesWithSearchLimit() {
populator.setMaxSearchDepth(1);
DirContextAdapter ctx = new DirContextAdapter(
"uid=scaladude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,
"scaladude");
assertEquals(1, authorities.size());
assertEquals(Arrays.asList(scalaDevelopers), authorities);
}
@Test
public void testClosureDudeJDevelopersWithMembershipAsAttributeValues() {
populator.setAttributeNames(new HashSet(Arrays.asList("member")));
@Test
public void testGroovyDudeJDevelopersAuthorities() {
DirContextAdapter ctx = new DirContextAdapter(
"uid=groovydude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,
"groovydude");
assertEquals(4, authorities.size());
assertEquals(Arrays.asList(javaDevelopers, circularJavaDevelopers, jDevelopers,
groovyDevelopers), authorities);
}
DirContextAdapter ctx = new DirContextAdapter("uid=closuredude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "closuredude");
assertEquals(5, authorities.size());
assertEquals(Arrays.asList(closureDevelopers, javaDevelopers, circularJavaDevelopers, jDevelopers, groovyDevelopers), authorities);
@Test
public void testClosureDudeJDevelopersWithMembershipAsAttributeValues() {
populator.setAttributeNames(new HashSet(Arrays.asList("member")));
LdapAuthority[] ldapAuthorities = authorities.toArray(new LdapAuthority[0]);
assertEquals(5, ldapAuthorities.length);
//closure group
assertTrue(ldapAuthorities[0].getAttributes().containsKey("member"));
assertNotNull(ldapAuthorities[0].getAttributes().get("member"));
assertEquals(1, ldapAuthorities[0].getAttributes().get("member").size());
assertEquals("uid=closuredude,ou=people,dc=springframework,dc=org", ldapAuthorities[0].getFirstAttributeValue("member"));
DirContextAdapter ctx = new DirContextAdapter(
"uid=closuredude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,
"closuredude");
assertEquals(5, authorities.size());
assertEquals(Arrays.asList(closureDevelopers, javaDevelopers,
circularJavaDevelopers, jDevelopers, groovyDevelopers), authorities);
//java group
assertTrue(ldapAuthorities[1].getAttributes().containsKey("member"));
assertNotNull(ldapAuthorities[1].getAttributes().get("member"));
assertEquals(3, ldapAuthorities[1].getAttributes().get("member").size());
assertEquals(groovyDevelopers.getDn(), ldapAuthorities[1].getFirstAttributeValue("member"));
assertEquals(
new String[]{
groovyDevelopers.getDn(),
scalaDevelopers.getDn(),
"uid=javadude,ou=people,dc=springframework,dc=org"
},
ldapAuthorities[1].getAttributes().get("member")
);
LdapAuthority[] ldapAuthorities = authorities.toArray(new LdapAuthority[0]);
assertEquals(5, ldapAuthorities.length);
// closure group
assertTrue(ldapAuthorities[0].getAttributes().containsKey("member"));
assertNotNull(ldapAuthorities[0].getAttributes().get("member"));
assertEquals(1, ldapAuthorities[0].getAttributes().get("member").size());
assertEquals("uid=closuredude,ou=people,dc=springframework,dc=org",
ldapAuthorities[0].getFirstAttributeValue("member"));
//test non existent attribute
assertNull(ldapAuthorities[2].getFirstAttributeValue("test"));
assertNotNull(ldapAuthorities[2].getAttributeValues("test"));
assertEquals(0, ldapAuthorities[2].getAttributeValues("test").size());
//test role name
assertEquals(jDevelopers.getAuthority(), ldapAuthorities[3].getAuthority());
}
// java group
assertTrue(ldapAuthorities[1].getAttributes().containsKey("member"));
assertNotNull(ldapAuthorities[1].getAttributes().get("member"));
assertEquals(3, ldapAuthorities[1].getAttributes().get("member").size());
assertEquals(groovyDevelopers.getDn(),
ldapAuthorities[1].getFirstAttributeValue("member"));
assertEquals(new String[] { groovyDevelopers.getDn(), scalaDevelopers.getDn(),
"uid=javadude,ou=people,dc=springframework,dc=org" }, ldapAuthorities[1]
.getAttributes().get("member"));
// test non existent attribute
assertNull(ldapAuthorities[2].getFirstAttributeValue("test"));
assertNotNull(ldapAuthorities[2].getAttributeValues("test"));
assertEquals(0, ldapAuthorities[2].getAttributeValues("test").size());
// test role name
assertEquals(jDevelopers.getAuthority(), ldapAuthorities[3].getAuthority());
}
}
@@ -3,34 +3,34 @@ package org.springframework.security.ldap;
import org.springframework.ldap.core.DistinguishedName;
/**
* This implementation appends a name component to the <tt>userDnBase</tt> context using the
* <tt>usernameAttributeName</tt> property. So if the <tt>uid</tt> attribute is used to store the username, and the
* base DN is <tt>cn=users</tt> and we are creating a new user called "sam", then the DN will be
* <tt>uid=sam,cn=users</tt>.
* This implementation appends a name component to the <tt>userDnBase</tt> context using
* the <tt>usernameAttributeName</tt> property. So if the <tt>uid</tt> attribute is used
* to store the username, and the base DN is <tt>cn=users</tt> and we are creating a new
* user called "sam", then the DN will be <tt>uid=sam,cn=users</tt>.
*
* @author Luke Taylor
*/
public class DefaultLdapUsernameToDnMapper implements LdapUsernameToDnMapper {
private final String userDnBase;
private final String usernameAttribute;
private final String userDnBase;
private final String usernameAttribute;
/**
* @param userDnBase the base name of the DN
* @param usernameAttribute the attribute to append for the username component.
*/
public DefaultLdapUsernameToDnMapper(String userDnBase, String usernameAttribute) {
this.userDnBase = userDnBase;
this.usernameAttribute = usernameAttribute;
}
/**
* @param userDnBase the base name of the DN
* @param usernameAttribute the attribute to append for the username component.
*/
public DefaultLdapUsernameToDnMapper(String userDnBase, String usernameAttribute) {
this.userDnBase = userDnBase;
this.usernameAttribute = usernameAttribute;
}
/**
* Assembles the Distinguished Name that should be used the given username.
*/
public DistinguishedName buildDn(String username) {
DistinguishedName dn = new DistinguishedName(userDnBase);
/**
* Assembles the Distinguished Name that should be used the given username.
*/
public DistinguishedName buildDn(String username) {
DistinguishedName dn = new DistinguishedName(userDnBase);
dn.add(usernameAttribute, username);
dn.add(usernameAttribute, username);
return dn;
}
return dn;
}
}
@@ -13,127 +13,143 @@ import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrat
import org.springframework.util.Assert;
/**
* ContextSource implementation which uses Spring LDAP's <tt>LdapContextSource</tt> as a base
* class. Used internally by the Spring Security LDAP namespace configuration.
* ContextSource implementation which uses Spring LDAP's <tt>LdapContextSource</tt> as a
* base class. Used internally by the Spring Security LDAP namespace configuration.
* <p>
* From Spring Security 3.0, Spring LDAP 1.3 is used and the <tt>ContextSource</tt> interface
* provides support for binding with a username and password. As a result, Spring LDAP <tt>ContextSource</tt>
* implementations such as <tt>LdapContextSource</tt> may be used directly with Spring Security.
* From Spring Security 3.0, Spring LDAP 1.3 is used and the <tt>ContextSource</tt>
* interface provides support for binding with a username and password. As a result,
* Spring LDAP <tt>ContextSource</tt> implementations such as <tt>LdapContextSource</tt>
* may be used directly with Spring Security.
* <p>
* Spring LDAP 1.3 doesn't have JVM-level LDAP connection pooling enabled by default. This class sets the
* <tt>pooled</tt> property to true, but customizes the {@link DirContextAuthenticationStrategy} used to disable
* pooling when the <tt>DN</tt> doesn't match the <tt>userDn</tt> property. This prevents pooling for calls
* to {@link #getContext(String, String)} to authenticate as specific users.
* Spring LDAP 1.3 doesn't have JVM-level LDAP connection pooling enabled by default. This
* class sets the <tt>pooled</tt> property to true, but customizes the
* {@link DirContextAuthenticationStrategy} used to disable pooling when the <tt>DN</tt>
* doesn't match the <tt>userDn</tt> property. This prevents pooling for calls to
* {@link #getContext(String, String)} to authenticate as specific users.
*
* @author Luke Taylor
* @since 2.0
*/
public class DefaultSpringSecurityContextSource extends LdapContextSource {
protected final Log logger = LogFactory.getLog(getClass());
protected final Log logger = LogFactory.getLog(getClass());
private String rootDn;
private String rootDn;
/**
* Create and initialize an instance which will connect to the supplied LDAP URL. If you
* want to use more than one server for fail-over, rather use
* the {@link #DefaultSpringSecurityContextSource(List, String)} constructor.
*
* @param providerUrl an LDAP URL of the form <code>ldap://localhost:389/base_dn<code>
*/
public DefaultSpringSecurityContextSource(String providerUrl) {
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
/**
* Create and initialize an instance which will connect to the supplied LDAP URL. If
* you want to use more than one server for fail-over, rather use the
* {@link #DefaultSpringSecurityContextSource(List, String)} constructor.
*
* @param providerUrl an LDAP URL of the form <code>ldap://localhost:389/base_dn<code>
*/
public DefaultSpringSecurityContextSource(String providerUrl) {
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
StringTokenizer st = new StringTokenizer(providerUrl);
StringTokenizer st = new StringTokenizer(providerUrl);
ArrayList<String> urls = new ArrayList<String>();
ArrayList<String> urls = new ArrayList<String>();
// Work out rootDn from the first URL and check that the other URLs (if any) match
while (st.hasMoreTokens()) {
String url = st.nextToken();
String urlRootDn = LdapUtils.parseRootDnFromUrl(url);
// Work out rootDn from the first URL and check that the other URLs (if any) match
while (st.hasMoreTokens()) {
String url = st.nextToken();
String urlRootDn = LdapUtils.parseRootDnFromUrl(url);
urls.add(url.substring(0, url.lastIndexOf(urlRootDn)));
urls.add(url.substring(0, url.lastIndexOf(urlRootDn)));
logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'");
logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'");
if (rootDn == null) {
rootDn = urlRootDn;
} else if (!rootDn.equals(urlRootDn)) {
throw new IllegalArgumentException("Root DNs must be the same when using multiple URLs");
}
}
if (rootDn == null) {
rootDn = urlRootDn;
}
else if (!rootDn.equals(urlRootDn)) {
throw new IllegalArgumentException(
"Root DNs must be the same when using multiple URLs");
}
}
setUrls(urls.toArray(new String[urls.size()]));
setBase(rootDn);
setPooled(true);
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
@Override
@SuppressWarnings("rawtypes")
public void setupEnvironment(Hashtable env, String dn, String password) {
super.setupEnvironment(env, dn, password);
// Remove the pooling flag unless we are authenticating as the 'manager' user.
if (!userDn.equals(dn) && env.containsKey(SUN_LDAP_POOLING_FLAG)) {
logger.debug("Removing pooling flag for user " + dn);
env.remove(SUN_LDAP_POOLING_FLAG);
}
}
});
}
setUrls(urls.toArray(new String[urls.size()]));
setBase(rootDn);
setPooled(true);
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
@Override
@SuppressWarnings("rawtypes")
public void setupEnvironment(Hashtable env, String dn, String password) {
super.setupEnvironment(env, dn, password);
// Remove the pooling flag unless we are authenticating as the 'manager'
// user.
if (!userDn.equals(dn) && env.containsKey(SUN_LDAP_POOLING_FLAG)) {
logger.debug("Removing pooling flag for user " + dn);
env.remove(SUN_LDAP_POOLING_FLAG);
}
}
});
}
/**
* Create and initialize an instance which will connect of the LDAP Spring Security
* Context Source. It will connect to any of the provided LDAP server URLs.
*
* @param urls
* A list of string values which are LDAP server URLs. An example would be
* <code>ldap://ldap.company.com:389</code>. LDAPS URLs (SSL-secured) may be used as well,
* given that Spring Security is able to connect to the server.
* Note that these <b>URLs must not include the base DN</b>!
* @param baseDn
* The common Base DN for all provided servers, e.g.
* <pre>dc=company,dc=com</pre>.
*/
public DefaultSpringSecurityContextSource(List<String> urls, String baseDn) {
this(buildProviderUrl(urls, baseDn));
}
/**
* Create and initialize an instance which will connect of the LDAP Spring Security
* Context Source. It will connect to any of the provided LDAP server URLs.
*
* @param urls A list of string values which are LDAP server URLs. An example would be
* <code>ldap://ldap.company.com:389</code>. LDAPS URLs (SSL-secured) may be used as
* well, given that Spring Security is able to connect to the server. Note that these
* <b>URLs must not include the base DN</b>!
* @param baseDn The common Base DN for all provided servers, e.g.
*
* <pre>
* dc=company,dc=com
* </pre>
*
* .
*/
public DefaultSpringSecurityContextSource(List<String> urls, String baseDn) {
this(buildProviderUrl(urls, baseDn));
}
/**
* Builds a Spring LDAP-compliant Provider URL string, i.e. a space-separated list of LDAP servers
* with their base DNs. As the base DN must be identical for all servers, it needs to be supplied
* only once.
*
* @param urls
* A list of string values which are LDAP server URLs. An example would be
* <pre>ldap://ldap.company.com:389</pre>. LDAPS URLs may be used as well,
* given that Spring Security is able to connect to the server.
* @param baseDn
* The common Base DN for all provided servers, e.g.
* <pre>dc=company,dc=com</pre>.
* @return A Spring Security/Spring LDAP-compliant Provider URL string.
*/
private static String buildProviderUrl(List<String> urls, String baseDn) {
Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null.");
Assert.notEmpty(urls, "At least one LDAP server URL must be provided.");
/**
* Builds a Spring LDAP-compliant Provider URL string, i.e. a space-separated list of
* LDAP servers with their base DNs. As the base DN must be identical for all servers,
* it needs to be supplied only once.
*
* @param urls A list of string values which are LDAP server URLs. An example would be
*
* <pre>
* ldap://ldap.company.com:389
* </pre>
*
* . LDAPS URLs may be used as well, given that Spring Security is able to connect to
* the server.
* @param baseDn The common Base DN for all provided servers, e.g.
*
* <pre>
* dc=company,dc=com
* </pre>
*
* .
* @return A Spring Security/Spring LDAP-compliant Provider URL string.
*/
private static String buildProviderUrl(List<String> urls, String baseDn) {
Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null.");
Assert.notEmpty(urls, "At least one LDAP server URL must be provided.");
String trimmedBaseDn = baseDn.trim();
StringBuilder providerUrl = new StringBuilder();
String trimmedBaseDn = baseDn.trim();
StringBuilder providerUrl = new StringBuilder();
for (String serverUrl : urls) {
String trimmedUrl = serverUrl.trim();
if ("".equals(trimmedUrl)) {
continue;
}
for (String serverUrl : urls) {
String trimmedUrl = serverUrl.trim();
if ("".equals(trimmedUrl)) {
continue;
}
providerUrl.append(trimmedUrl);
if (! trimmedUrl.endsWith("/")) {
providerUrl.append("/");
}
providerUrl.append(trimmedBaseDn);
providerUrl.append(" ");
}
providerUrl.append(trimmedUrl);
if (!trimmedUrl.endsWith("/")) {
providerUrl.append("/");
}
providerUrl.append(trimmedBaseDn);
providerUrl.append(" ");
}
return providerUrl.toString();
return providerUrl.toString();
}
}
}
@@ -22,8 +22,8 @@ import org.springframework.ldap.BadLdapGrammarException;
* Helper class to encode and decode ldap names and values.
*
* <p>
* NOTE: This is a copy from Spring LDAP so that both Spring LDAP 1.x and 2.x
* can be supported without reflection.
* NOTE: This is a copy from Spring LDAP so that both Spring LDAP 1.x and 2.x can be
* supported without reflection.
* </p>
*
* @author Adam Skogman
@@ -31,207 +31,211 @@ import org.springframework.ldap.BadLdapGrammarException;
*/
final class LdapEncoder {
private static final int HEX = 16;
private static String[] NAME_ESCAPE_TABLE = new String[96];
private static final int HEX = 16;
private static String[] NAME_ESCAPE_TABLE = new String[96];
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
static {
static {
// Name encoding table -------------------------------------
// Name encoding table -------------------------------------
// all below 0x20 (control chars)
for (char c = 0; c < ' '; c++) {
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
}
// all below 0x20 (control chars)
for (char c = 0; c < ' '; c++) {
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
}
NAME_ESCAPE_TABLE['#'] = "\\#";
NAME_ESCAPE_TABLE[','] = "\\,";
NAME_ESCAPE_TABLE[';'] = "\\;";
NAME_ESCAPE_TABLE['='] = "\\=";
NAME_ESCAPE_TABLE['+'] = "\\+";
NAME_ESCAPE_TABLE['<'] = "\\<";
NAME_ESCAPE_TABLE['>'] = "\\>";
NAME_ESCAPE_TABLE['\"'] = "\\\"";
NAME_ESCAPE_TABLE['\\'] = "\\\\";
NAME_ESCAPE_TABLE['#'] = "\\#";
NAME_ESCAPE_TABLE[','] = "\\,";
NAME_ESCAPE_TABLE[';'] = "\\;";
NAME_ESCAPE_TABLE['='] = "\\=";
NAME_ESCAPE_TABLE['+'] = "\\+";
NAME_ESCAPE_TABLE['<'] = "\\<";
NAME_ESCAPE_TABLE['>'] = "\\>";
NAME_ESCAPE_TABLE['\"'] = "\\\"";
NAME_ESCAPE_TABLE['\\'] = "\\\\";
// Filter encoding table -------------------------------------
// Filter encoding table -------------------------------------
// fill with char itself
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
}
// fill with char itself
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
}
// escapes (RFC2254)
FILTER_ESCAPE_TABLE['*'] = "\\2a";
FILTER_ESCAPE_TABLE['('] = "\\28";
FILTER_ESCAPE_TABLE[')'] = "\\29";
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
FILTER_ESCAPE_TABLE[0] = "\\00";
// escapes (RFC2254)
FILTER_ESCAPE_TABLE['*'] = "\\2a";
FILTER_ESCAPE_TABLE['('] = "\\28";
FILTER_ESCAPE_TABLE[')'] = "\\29";
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
FILTER_ESCAPE_TABLE[0] = "\\00";
}
}
/**
* All static methods - not to be instantiated.
*/
private LdapEncoder() {
}
/**
* All static methods - not to be instantiated.
*/
private LdapEncoder() {
}
protected static String toTwoCharHex(char c) {
protected static String toTwoCharHex(char c) {
String raw = Integer.toHexString(c).toUpperCase();
String raw = Integer.toHexString(c).toUpperCase();
if (raw.length() > 1) {
return raw;
} else {
return "0" + raw;
}
}
if (raw.length() > 1) {
return raw;
}
else {
return "0" + raw;
}
}
/**
* Escape a value for use in a filter.
*
* @param value
* the value to escape.
* @return a properly escaped representation of the supplied value.
*/
public static String filterEncode(String value) {
/**
* Escape a value for use in a filter.
*
* @param value the value to escape.
* @return a properly escaped representation of the supplied value.
*/
public static String filterEncode(String value) {
if (value == null)
return null;
if (value == null)
return null;
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length();
int length = value.length();
for (int i = 0; i < length; i++) {
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
char c = value.charAt(i);
if (c < FILTER_ESCAPE_TABLE.length) {
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
} else {
// default: add the char
encodedValue.append(c);
}
}
if (c < FILTER_ESCAPE_TABLE.length) {
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
}
else {
// default: add the char
encodedValue.append(c);
}
}
return encodedValue.toString();
}
return encodedValue.toString();
}
/**
* LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
*
* <br/>Escapes:<br/> ' ' [space] - "\ " [if first or last] <br/> '#'
* [hash] - "\#" <br/> ',' [comma] - "\," <br/> ';' [semicolon] - "\;" <br/> '=
* [equals] - "\=" <br/> '+' [plus] - "\+" <br/> '&lt;' [less than] -
* "\&lt;" <br/> '&gt;' [greater than] - "\&gt;" <br/> '"' [double quote] -
* "\"" <br/> '\' [backslash] - "\\" <br/>
*
* @param value
* the value to escape.
* @return The escaped value.
*/
public static String nameEncode(String value) {
/**
* LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
*
* <br/>
* Escapes:<br/>
* ' ' [space] - "\ " [if first or last] <br/>
* '#' [hash] - "\#" <br/>
* ',' [comma] - "\," <br/>
* ';' [semicolon] - "\;" <br/>
* '= [equals] - "\=" <br/>
* '+' [plus] - "\+" <br/>
* '&lt;' [less than] - "\&lt;" <br/>
* '&gt;' [greater than] - "\&gt;" <br/>
* '"' [double quote] - "\"" <br/>
* '\' [backslash] - "\\" <br/>
*
* @param value the value to escape.
* @return The escaped value.
*/
public static String nameEncode(String value) {
if (value == null)
return null;
if (value == null)
return null;
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length();
int last = length - 1;
int length = value.length();
int last = length - 1;
for (int i = 0; i < length; i++) {
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
char c = value.charAt(i);
// space first or last
if (c == ' ' && (i == 0 || i == last)) {
encodedValue.append("\\ ");
continue;
}
// space first or last
if (c == ' ' && (i == 0 || i == last)) {
encodedValue.append("\\ ");
continue;
}
if (c < NAME_ESCAPE_TABLE.length) {
// check in table for escapes
String esc = NAME_ESCAPE_TABLE[c];
if (c < NAME_ESCAPE_TABLE.length) {
// check in table for escapes
String esc = NAME_ESCAPE_TABLE[c];
if (esc != null) {
encodedValue.append(esc);
continue;
}
}
if (esc != null) {
encodedValue.append(esc);
continue;
}
}
// default: add the char
encodedValue.append(c);
}
// default: add the char
encodedValue.append(c);
}
return encodedValue.toString();
return encodedValue.toString();
}
}
/**
* Decodes a value. Converts escaped chars to ordinary chars.
*
* @param value
* Trimmed value, so no leading an trailing blanks, except an
* escaped space last.
* @return The decoded value as a string.
* @throws BadLdapGrammarException
*/
static public String nameDecode(String value)
throws BadLdapGrammarException {
/**
* Decodes a value. Converts escaped chars to ordinary chars.
*
* @param value Trimmed value, so no leading an trailing blanks, except an escaped
* space last.
* @return The decoded value as a string.
* @throws BadLdapGrammarException
*/
static public String nameDecode(String value) throws BadLdapGrammarException {
if (value == null)
return null;
if (value == null)
return null;
// make buffer same size
StringBuilder decoded = new StringBuilder(value.length());
// make buffer same size
StringBuilder decoded = new StringBuilder(value.length());
int i = 0;
while (i < value.length()) {
char currentChar = value.charAt(i);
if (currentChar == '\\') {
if (value.length() <= i + 1) {
// Ending with a single backslash is not allowed
throw new BadLdapGrammarException(
"Unexpected end of value " + "unterminated '\\'");
} else {
char nextChar = value.charAt(i + 1);
if (nextChar == ',' || nextChar == '=' || nextChar == '+'
|| nextChar == '<' || nextChar == '>'
|| nextChar == '#' || nextChar == ';'
|| nextChar == '\\' || nextChar == '\"'
|| nextChar == ' ') {
// Normal backslash escape
decoded.append(nextChar);
i += 2;
} else {
if (value.length() <= i + 2) {
throw new BadLdapGrammarException(
"Unexpected end of value "
+ "expected special or hex, found '"
+ nextChar + "'");
} else {
// This should be a hex value
String hexString = "" + nextChar
+ value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString,
HEX));
i += 3;
}
}
}
} else {
// This character wasn't escaped - just append it
decoded.append(currentChar);
i++;
}
}
int i = 0;
while (i < value.length()) {
char currentChar = value.charAt(i);
if (currentChar == '\\') {
if (value.length() <= i + 1) {
// Ending with a single backslash is not allowed
throw new BadLdapGrammarException("Unexpected end of value "
+ "unterminated '\\'");
}
else {
char nextChar = value.charAt(i + 1);
if (nextChar == ',' || nextChar == '=' || nextChar == '+'
|| nextChar == '<' || nextChar == '>' || nextChar == '#'
|| nextChar == ';' || nextChar == '\\' || nextChar == '\"'
|| nextChar == ' ') {
// Normal backslash escape
decoded.append(nextChar);
i += 2;
}
else {
if (value.length() <= i + 2) {
throw new BadLdapGrammarException("Unexpected end of value "
+ "expected special or hex, found '" + nextChar + "'");
}
else {
// This should be a hex value
String hexString = "" + nextChar + value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString, HEX));
i += 3;
}
}
}
}
else {
// This character wasn't escaped - just append it
decoded.append(currentChar);
i++;
}
}
return decoded.toString();
return decoded.toString();
}
}
}
@@ -8,5 +8,5 @@ import org.springframework.ldap.core.DistinguishedName;
* @author Luke Taylor
*/
public interface LdapUsernameToDnMapper {
DistinguishedName buildDn(String username);
DistinguishedName buildDn(String username);
}
@@ -29,156 +29,173 @@ import javax.naming.NamingException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* LDAP Utility methods.
*
* @author Luke Taylor
*/
public final class LdapUtils {
//~ Static fields/initializers =====================================================================================
// ~ Static fields/initializers
// =====================================================================================
private static final Log logger = LogFactory.getLog(LdapUtils.class);
private static final Log logger = LogFactory.getLog(LdapUtils.class);
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
private LdapUtils() {
}
private LdapUtils() {
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
public static void closeContext(Context ctx) {
if(ctx instanceof DirContextAdapter) {
return;
}
public static void closeContext(Context ctx) {
if (ctx instanceof DirContextAdapter) {
return;
}
try {
if (ctx != null) {
ctx.close();
}
} catch (NamingException e) {
logger.error("Failed to close context.", e);
}
}
try {
if (ctx != null) {
ctx.close();
}
}
catch (NamingException e) {
logger.error("Failed to close context.", e);
}
}
public static void closeEnumeration(NamingEnumeration ne) {
try {
if (ne != null) {
ne.close();
}
} catch (NamingException e) {
logger.error("Failed to close enumeration.", e);
}
}
public static void closeEnumeration(NamingEnumeration ne) {
try {
if (ne != null) {
ne.close();
}
}
catch (NamingException e) {
logger.error("Failed to close enumeration.", e);
}
}
/**
* Obtains the part of a DN relative to a supplied base context.
* <p>If the DN is "cn=bob,ou=people,dc=springframework,dc=org" and the base context name is
* "ou=people,dc=springframework,dc=org" it would return "cn=bob".
* </p>
*
* @param fullDn the DN
* @param baseCtx the context to work out the name relative to.
*
* @return the
*
* @throws NamingException any exceptions thrown by the context are propagated.
*/
public static String getRelativeName(String fullDn, Context baseCtx) throws NamingException {
/**
* Obtains the part of a DN relative to a supplied base context.
* <p>
* If the DN is "cn=bob,ou=people,dc=springframework,dc=org" and the base context name
* is "ou=people,dc=springframework,dc=org" it would return "cn=bob".
* </p>
*
* @param fullDn the DN
* @param baseCtx the context to work out the name relative to.
*
* @return the
*
* @throws NamingException any exceptions thrown by the context are propagated.
*/
public static String getRelativeName(String fullDn, Context baseCtx)
throws NamingException {
String baseDn = baseCtx.getNameInNamespace();
String baseDn = baseCtx.getNameInNamespace();
if (baseDn.length() == 0) {
return fullDn;
}
if (baseDn.length() == 0) {
return fullDn;
}
DistinguishedName base = new DistinguishedName(baseDn);
DistinguishedName full = new DistinguishedName(fullDn);
DistinguishedName base = new DistinguishedName(baseDn);
DistinguishedName full = new DistinguishedName(fullDn);
if(base.equals(full)) {
return "";
}
if (base.equals(full)) {
return "";
}
Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN");
Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN");
full.removeFirst(base);
full.removeFirst(base);
return full.toString();
}
return full.toString();
}
/**
* Gets the full dn of a name by prepending the name of the context it is relative to.
* If the name already contains the base name, it is returned unaltered.
*/
public static DistinguishedName getFullDn(DistinguishedName dn, Context baseCtx)
throws NamingException {
DistinguishedName baseDn = new DistinguishedName(baseCtx.getNameInNamespace());
/**
* Gets the full dn of a name by prepending the name of the context it is relative to.
* If the name already contains the base name, it is returned unaltered.
*/
public static DistinguishedName getFullDn(DistinguishedName dn, Context baseCtx)
throws NamingException {
DistinguishedName baseDn = new DistinguishedName(baseCtx.getNameInNamespace());
if(dn.contains(baseDn)) {
return dn;
}
if (dn.contains(baseDn)) {
return dn;
}
baseDn.append(dn);
baseDn.append(dn);
return baseDn;
}
return baseDn;
}
public static String convertPasswordToString(Object passObj) {
Assert.notNull(passObj, "Password object to convert must not be null");
public static String convertPasswordToString(Object passObj) {
Assert.notNull(passObj, "Password object to convert must not be null");
if(passObj instanceof byte[]) {
return Utf8.decode((byte[])passObj);
} else if (passObj instanceof String) {
return (String)passObj;
} else {
throw new IllegalArgumentException("Password object was not a String or byte array.");
}
}
if (passObj instanceof byte[]) {
return Utf8.decode((byte[]) passObj);
}
else if (passObj instanceof String) {
return (String) passObj;
}
else {
throw new IllegalArgumentException(
"Password object was not a String or byte array.");
}
}
/**
* Works out the root DN for an LDAP URL.<p>For example, the URL
* <tt>ldap://monkeymachine:11389/dc=springframework,dc=org</tt> has the root DN "dc=springframework,dc=org".</p>
*
* @param url the LDAP URL
*
* @return the root DN
*/
public static String parseRootDnFromUrl(String url) {
Assert.hasLength(url);
/**
* Works out the root DN for an LDAP URL.
* <p>
* For example, the URL <tt>ldap://monkeymachine:11389/dc=springframework,dc=org</tt>
* has the root DN "dc=springframework,dc=org".
* </p>
*
* @param url the LDAP URL
*
* @return the root DN
*/
public static String parseRootDnFromUrl(String url) {
Assert.hasLength(url);
String urlRootDn;
String urlRootDn;
if (url.startsWith("ldap:") || url.startsWith("ldaps:")) {
URI uri = parseLdapUrl(url);
urlRootDn = uri.getRawPath();
} else {
// Assume it's an embedded server
urlRootDn = url;
}
if (url.startsWith("ldap:") || url.startsWith("ldaps:")) {
URI uri = parseLdapUrl(url);
urlRootDn = uri.getRawPath();
}
else {
// Assume it's an embedded server
urlRootDn = url;
}
if (urlRootDn.startsWith("/")) {
urlRootDn = urlRootDn.substring(1);
}
if (urlRootDn.startsWith("/")) {
urlRootDn = urlRootDn.substring(1);
}
return urlRootDn;
}
return urlRootDn;
}
/**
* Parses the supplied LDAP URL.
* @param url the URL (e.g. <tt>ldap://monkeymachine:11389/dc=springframework,dc=org</tt>).
* @return the URI object created from the URL
* @throws IllegalArgumentException if the URL is null, empty or the URI syntax is invalid.
*/
/**
* Parses the supplied LDAP URL.
* @param url the URL (e.g.
* <tt>ldap://monkeymachine:11389/dc=springframework,dc=org</tt>).
* @return the URI object created from the URL
* @throws IllegalArgumentException if the URL is null, empty or the URI syntax is
* invalid.
*/
private static URI parseLdapUrl(String url) {
Assert.hasLength(url);
private static URI parseLdapUrl(String url) {
Assert.hasLength(url);
try {
return new URI(url);
} catch (URISyntaxException e) {
IllegalArgumentException iae = new IllegalArgumentException("Unable to parse url: " + url);
iae.initCause(e);
throw iae;
}
}
try {
return new URI(url);
}
catch (URISyntaxException e) {
IllegalArgumentException iae = new IllegalArgumentException(
"Unable to parse url: " + url);
iae.initCause(e);
throw iae;
}
}
}
@@ -44,9 +44,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Extension of Spring LDAP's LdapTemplate class which adds extra functionality required by Spring Security.
* Extension of Spring LDAP's LdapTemplate class which adds extra functionality required
* by Spring Security.
*
* @author Ben Alex
* @author Luke Taylor
@@ -54,310 +54,340 @@ import java.util.Set;
* @since 2.0
*/
public class SpringSecurityLdapTemplate extends LdapTemplate {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(SpringSecurityLdapTemplate.class);
// ~ Static fields/initializers
// =====================================================================================
private static final Log logger = LogFactory.getLog(SpringSecurityLdapTemplate.class);
public static final String[] NO_ATTRS = new String[0];
/**
* Every search results where a record is defined by a Map&lt;String,String[]&gt;
* contains at least this key - the DN of the record itself.
*/
public static final String DN_KEY = "spring.security.ldap.dn";
public static final String[] NO_ATTRS = new String[0];
private static final boolean RETURN_OBJECT = true;
/**
* Every search results where a record is defined by a Map&lt;String,String[]&gt;
* contains at least this key - the DN of the record itself.
*/
public static final String DN_KEY = "spring.security.ldap.dn";
//~ Instance fields ================================================================================================
private static final boolean RETURN_OBJECT = true;
/** Default search controls */
private SearchControls searchControls = new SearchControls();
// ~ Instance fields
// ================================================================================================
//~ Constructors ===================================================================================================
/** Default search controls */
private SearchControls searchControls = new SearchControls();
public SpringSecurityLdapTemplate(ContextSource contextSource) {
Assert.notNull(contextSource, "ContextSource cannot be null");
setContextSource(contextSource);
// ~ Constructors
// ===================================================================================================
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
}
public SpringSecurityLdapTemplate(ContextSource contextSource) {
Assert.notNull(contextSource, "ContextSource cannot be null");
setContextSource(contextSource);
//~ Methods ========================================================================================================
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
}
/**
* Performs an LDAP compare operation of the value of an attribute for a particular directory entry.
*
* @param dn the entry who's attribute is to be used
* @param attributeName the attribute who's value we want to compare
* @param value the value to be checked against the directory value
*
* @return true if the supplied value matches that in the directory
*/
public boolean compare(final String dn, final String attributeName, final Object value) {
final String comparisonFilter = "(" + attributeName + "={0})";
// ~ Methods
// ========================================================================================================
class LdapCompareCallback implements ContextExecutor {
/**
* Performs an LDAP compare operation of the value of an attribute for a particular
* directory entry.
*
* @param dn the entry who's attribute is to be used
* @param attributeName the attribute who's value we want to compare
* @param value the value to be checked against the directory value
*
* @return true if the supplied value matches that in the directory
*/
public boolean compare(final String dn, final String attributeName, final Object value) {
final String comparisonFilter = "(" + attributeName + "={0})";
public Object executeWithContext(DirContext ctx) throws NamingException {
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(NO_ATTRS);
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
class LdapCompareCallback implements ContextExecutor {
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, new Object[] {value}, ctls);
public Object executeWithContext(DirContext ctx) throws NamingException {
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(NO_ATTRS);
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
Boolean match = Boolean.valueOf(results.hasMore());
LdapUtils.closeEnumeration(results);
NamingEnumeration<SearchResult> results = ctx.search(dn,
comparisonFilter, new Object[] { value }, ctls);
return match;
}
}
Boolean match = Boolean.valueOf(results.hasMore());
LdapUtils.closeEnumeration(results);
Boolean matches = (Boolean) executeReadOnly(new LdapCompareCallback());
return match;
}
}
return matches.booleanValue();
}
Boolean matches = (Boolean) executeReadOnly(new LdapCompareCallback());
/**
* Composes an object from the attributes of the given DN.
*
* @param dn the directory entry which will be read
* @param attributesToRetrieve the named attributes which will be retrieved from the directory entry.
*
* @return the object created by the mapper
*/
public DirContextOperations retrieveEntry(final String dn, final String[] attributesToRetrieve) {
return matches.booleanValue();
}
return (DirContextOperations) executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
/**
* Composes an object from the attributes of the given DN.
*
* @param dn the directory entry which will be read
* @param attributesToRetrieve the named attributes which will be retrieved from the
* directory entry.
*
* @return the object created by the mapper
*/
public DirContextOperations retrieveEntry(final String dn,
final String[] attributesToRetrieve) {
// Object object = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
return (DirContextOperations) executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
return new DirContextAdapter(attrs, new DistinguishedName(dn),
new DistinguishedName(ctx.getNameInNamespace()));
}
});
}
// Object object = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
/**
* Performs a search using the supplied filter and returns the union of the values of the named attribute
* found in all entries matched by the search. Note that one directory entry may have several values for the
* attribute. Intended for role searches and similar scenarios.
*
* @param base the DN to search in
* @param filter search filter to use
* @param params the parameters to substitute in the search filter
* @param attributeName the attribute who's values are to be retrieved.
*
* @return the set of String values for the attribute as a union of the values found in all the matching entries.
*/
public Set<String> searchForSingleAttributeValues(final String base, final String filter, final Object[] params,
final String attributeName) {
String[] attributeNames = new String[]{attributeName};
Set<Map<String, List<String>>> multipleAttributeValues = searchForMultipleAttributeValues(base, filter, params, attributeNames);
Set<String> result = new HashSet<String>();
for (Map<String, List<String>> map : multipleAttributeValues) {
List<String> values = map.get(attributeName);
if (values != null) {
result.addAll(values);
}
}
return result;
}
return new DirContextAdapter(attrs, new DistinguishedName(dn),
new DistinguishedName(ctx.getNameInNamespace()));
}
});
}
/**
* Performs a search using the supplied filter and returns the values of each named attribute
* found in all entries matched by the search. Note that one directory entry may have several values for the
* attribute. Intended for role searches and similar scenarios.
*
* @param base the DN to search in
* @param filter search filter to use
* @param params the parameters to substitute in the search filter
* @param attributeNames the attributes' values that are to be retrieved.
*
* @return the set of String values for each attribute found in all the matching entries.
* The attribute name is the key for each set of values. In addition each map contains the DN as a String
* with the key predefined key {@link #DN_KEY}.
*/
public Set<Map<String, List<String>>> searchForMultipleAttributeValues(final String base, final String filter, final Object[] params,
final String[] attributeNames) {
// Escape the params acording to RFC2254
Object[] encodedParams = new String[params.length];
/**
* Performs a search using the supplied filter and returns the union of the values of
* the named attribute found in all entries matched by the search. Note that one
* directory entry may have several values for the attribute. Intended for role
* searches and similar scenarios.
*
* @param base the DN to search in
* @param filter search filter to use
* @param params the parameters to substitute in the search filter
* @param attributeName the attribute who's values are to be retrieved.
*
* @return the set of String values for the attribute as a union of the values found
* in all the matching entries.
*/
public Set<String> searchForSingleAttributeValues(final String base,
final String filter, final Object[] params, final String attributeName) {
String[] attributeNames = new String[] { attributeName };
Set<Map<String, List<String>>> multipleAttributeValues = searchForMultipleAttributeValues(
base, filter, params, attributeNames);
Set<String> result = new HashSet<String>();
for (Map<String, List<String>> map : multipleAttributeValues) {
List<String> values = map.get(attributeName);
if (values != null) {
result.addAll(values);
}
}
return result;
}
for (int i = 0; i < params.length; i++) {
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
}
/**
* Performs a search using the supplied filter and returns the values of each named
* attribute found in all entries matched by the search. Note that one directory entry
* may have several values for the attribute. Intended for role searches and similar
* scenarios.
*
* @param base the DN to search in
* @param filter search filter to use
* @param params the parameters to substitute in the search filter
* @param attributeNames the attributes' values that are to be retrieved.
*
* @return the set of String values for each attribute found in all the matching
* entries. The attribute name is the key for each set of values. In addition each map
* contains the DN as a String with the key predefined key {@link #DN_KEY}.
*/
public Set<Map<String, List<String>>> searchForMultipleAttributeValues(
final String base, final String filter, final Object[] params,
final String[] attributeNames) {
// Escape the params acording to RFC2254
Object[] encodedParams = new String[params.length];
String formattedFilter = MessageFormat.format(filter, encodedParams);
logger.debug("Using filter: " + formattedFilter);
for (int i = 0; i < params.length; i++) {
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
}
final HashSet<Map<String, List<String>>> set = new HashSet<Map<String, List<String>>>();
String formattedFilter = MessageFormat.format(filter, encodedParams);
logger.debug("Using filter: " + formattedFilter);
ContextMapper roleMapper = new ContextMapper() {
public Object mapFromContext(Object ctx) {
DirContextAdapter adapter = (DirContextAdapter) ctx;
Map<String, List<String>> record = new HashMap<String, List<String>>();
if (attributeNames == null || attributeNames.length == 0) {
try {
for (NamingEnumeration ae = adapter.getAttributes().getAll(); ae.hasMore(); ) {
Attribute attr = (Attribute) ae.next();
extractStringAttributeValues(adapter, record, attr.getID());
}
} catch (NamingException x) {
org.springframework.ldap.support.LdapUtils.convertLdapException(x);
}
} else {
for (String attributeName : attributeNames) {
extractStringAttributeValues(adapter, record, attributeName);
}
}
record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter)));
set.add(record);
return null;
}
};
final HashSet<Map<String, List<String>>> set = new HashSet<Map<String, List<String>>>();
SearchControls ctls = new SearchControls();
ctls.setSearchScope(searchControls.getSearchScope());
ctls.setReturningAttributes(attributeNames != null && attributeNames.length > 0 ? attributeNames : null);
ContextMapper roleMapper = new ContextMapper() {
public Object mapFromContext(Object ctx) {
DirContextAdapter adapter = (DirContextAdapter) ctx;
Map<String, List<String>> record = new HashMap<String, List<String>>();
if (attributeNames == null || attributeNames.length == 0) {
try {
for (NamingEnumeration ae = adapter.getAttributes().getAll(); ae
.hasMore();) {
Attribute attr = (Attribute) ae.next();
extractStringAttributeValues(adapter, record, attr.getID());
}
}
catch (NamingException x) {
org.springframework.ldap.support.LdapUtils
.convertLdapException(x);
}
}
else {
for (String attributeName : attributeNames) {
extractStringAttributeValues(adapter, record, attributeName);
}
}
record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter)));
set.add(record);
return null;
}
};
search(base, formattedFilter, ctls, roleMapper);
SearchControls ctls = new SearchControls();
ctls.setSearchScope(searchControls.getSearchScope());
ctls.setReturningAttributes(attributeNames != null && attributeNames.length > 0 ? attributeNames
: null);
return set;
}
search(base, formattedFilter, ctls, roleMapper);
/**
* Returns the DN for the context representing this LDAP record.
* By default this is using {@link javax.naming.Context#getNameInNamespace()}
* instead of {@link org.springframework.ldap.core.DirContextAdapter#getDn()} since the
* latter returns a partial DN if a base has been specified.
* @param adapter - the Context to extract the DN from
* @return - the String representing the full DN
*/
private String getAdapterDN(DirContextAdapter adapter) {
//returns the full DN rather than the sub DN if a base is specified
return adapter.getNameInNamespace();
}
return set;
}
/**
* Extracts String values for a specified attribute name and places them in the map representing the ldap record If
* a value is not of type String, it will derive it's value from the {@link Object#toString()}
*
* @param adapter - the adapter that contains the values
* @param record - the map holding the attribute names and values
* @param attributeName - the name for which to fetch the values from
*/
private void extractStringAttributeValues(DirContextAdapter adapter, Map<String, List<String>> record, String attributeName) {
Object[] values = adapter.getObjectAttributes(attributeName);
if (values == null || values.length == 0) {
if(logger.isDebugEnabled()) {
logger.debug("No attribute value found for '" + attributeName + "'");
}
return;
}
List<String> svalues = new ArrayList<String>();
for (Object o : values) {
if (o != null) {
if (String.class.isAssignableFrom(o.getClass())) {
svalues.add((String) o);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Attribute:" + attributeName + " contains a non string value of type[" + o.getClass() + "]");
}
svalues.add(o.toString());
}
}
}
record.put(attributeName, svalues);
}
/**
* Returns the DN for the context representing this LDAP record. By default this is
* using {@link javax.naming.Context#getNameInNamespace()} instead of
* {@link org.springframework.ldap.core.DirContextAdapter#getDn()} since the latter
* returns a partial DN if a base has been specified.
* @param adapter - the Context to extract the DN from
* @return - the String representing the full DN
*/
private String getAdapterDN(DirContextAdapter adapter) {
// returns the full DN rather than the sub DN if a base is specified
return adapter.getNameInNamespace();
}
/**
* Performs a search, with the requirement that the search shall return a single directory entry, and uses
* the supplied mapper to create the object from that entry.
* <p>
* Ignores <tt>PartialResultException</tt> if thrown, for compatibility with Active Directory
* (see {@link LdapTemplate#setIgnorePartialResultException(boolean)}).
*
* @param base the search base, relative to the base context supplied by the context source.
* @param filter the LDAP search filter
* @param params parameters to be substituted in the search.
*
* @return a DirContextOperations instance created from the matching entry.
*
* @throws IncorrectResultSizeDataAccessException if no results are found or the search returns more than one
* result.
*/
public DirContextOperations searchForSingleEntry(final String base, final String filter, final Object[] params) {
/**
* Extracts String values for a specified attribute name and places them in the map
* representing the ldap record If a value is not of type String, it will derive it's
* value from the {@link Object#toString()}
*
* @param adapter - the adapter that contains the values
* @param record - the map holding the attribute names and values
* @param attributeName - the name for which to fetch the values from
*/
private void extractStringAttributeValues(DirContextAdapter adapter,
Map<String, List<String>> record, String attributeName) {
Object[] values = adapter.getObjectAttributes(attributeName);
if (values == null || values.length == 0) {
if (logger.isDebugEnabled()) {
logger.debug("No attribute value found for '" + attributeName + "'");
}
return;
}
List<String> svalues = new ArrayList<String>();
for (Object o : values) {
if (o != null) {
if (String.class.isAssignableFrom(o.getClass())) {
svalues.add((String) o);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Attribute:" + attributeName
+ " contains a non string value of type[" + o.getClass()
+ "]");
}
svalues.add(o.toString());
}
}
}
record.put(attributeName, svalues);
}
return (DirContextOperations) executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
return searchForSingleEntryInternal(ctx, searchControls, base, filter, params);
}
});
}
/**
* Performs a search, with the requirement that the search shall return a single
* directory entry, and uses the supplied mapper to create the object from that entry.
* <p>
* Ignores <tt>PartialResultException</tt> if thrown, for compatibility with Active
* Directory (see {@link LdapTemplate#setIgnorePartialResultException(boolean)}).
*
* @param base the search base, relative to the base context supplied by the context
* source.
* @param filter the LDAP search filter
* @param params parameters to be substituted in the search.
*
* @return a DirContextOperations instance created from the matching entry.
*
* @throws IncorrectResultSizeDataAccessException if no results are found or the
* search returns more than one result.
*/
public DirContextOperations searchForSingleEntry(final String base,
final String filter, final Object[] params) {
/**
* Internal method extracted to avoid code duplication in AD search.
*/
public static DirContextOperations searchForSingleEntryInternal(DirContext ctx, SearchControls searchControls,
String base, String filter, Object[] params) throws NamingException {
final DistinguishedName ctxBaseDn = new DistinguishedName(ctx.getNameInNamespace());
final DistinguishedName searchBaseDn = new DistinguishedName(base);
final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params, buildControls(searchControls));
return (DirContextOperations) executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
return searchForSingleEntryInternal(ctx, searchControls, base, filter,
params);
}
});
}
if (logger.isDebugEnabled()) {
logger.debug("Searching for entry under DN '" + ctxBaseDn
+ "', base = '" + searchBaseDn + "', filter = '" + filter + "'");
}
/**
* Internal method extracted to avoid code duplication in AD search.
*/
public static DirContextOperations searchForSingleEntryInternal(DirContext ctx,
SearchControls searchControls, String base, String filter, Object[] params)
throws NamingException {
final DistinguishedName ctxBaseDn = new DistinguishedName(
ctx.getNameInNamespace());
final DistinguishedName searchBaseDn = new DistinguishedName(base);
final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn,
filter, params, buildControls(searchControls));
Set<DirContextOperations> results = new HashSet<DirContextOperations>();
try {
while (resultsEnum.hasMore()) {
SearchResult searchResult = resultsEnum.next();
DirContextAdapter dca = (DirContextAdapter) searchResult.getObject();
Assert.notNull(dca, "No object returned by search, DirContext is not correctly configured");
if (logger.isDebugEnabled()) {
logger.debug("Searching for entry under DN '" + ctxBaseDn + "', base = '"
+ searchBaseDn + "', filter = '" + filter + "'");
}
if (logger.isDebugEnabled()) {
logger.debug("Found DN: " + dca.getDn());
}
results.add(dca);
}
} catch (PartialResultException e) {
LdapUtils.closeEnumeration(resultsEnum);
logger.info("Ignoring PartialResultException");
}
Set<DirContextOperations> results = new HashSet<DirContextOperations>();
try {
while (resultsEnum.hasMore()) {
SearchResult searchResult = resultsEnum.next();
DirContextAdapter dca = (DirContextAdapter) searchResult.getObject();
Assert.notNull(dca,
"No object returned by search, DirContext is not correctly configured");
if (results.size() == 0) {
throw new IncorrectResultSizeDataAccessException(1, 0);
}
if (logger.isDebugEnabled()) {
logger.debug("Found DN: " + dca.getDn());
}
results.add(dca);
}
}
catch (PartialResultException e) {
LdapUtils.closeEnumeration(resultsEnum);
logger.info("Ignoring PartialResultException");
}
if (results.size() > 1) {
throw new IncorrectResultSizeDataAccessException(1, results.size());
}
if (results.size() == 0) {
throw new IncorrectResultSizeDataAccessException(1, 0);
}
return results.iterator().next();
}
if (results.size() > 1) {
throw new IncorrectResultSizeDataAccessException(1, results.size());
}
/**
* We need to make sure the search controls has the return object flag set to true, in order for
* the search to return DirContextAdapter instances.
* @param originalControls
* @return
*/
private static SearchControls buildControls(SearchControls originalControls) {
return new SearchControls(originalControls.getSearchScope(),
originalControls.getCountLimit(),
originalControls.getTimeLimit(),
originalControls.getReturningAttributes(),
RETURN_OBJECT,
originalControls.getDerefLinkFlag());
}
return results.iterator().next();
}
/**
* Sets the search controls which will be used for search operations by the template.
*
* @param searchControls the SearchControls instance which will be cached in the template.
*/
public void setSearchControls(SearchControls searchControls) {
this.searchControls = searchControls;
}
/**
* We need to make sure the search controls has the return object flag set to true, in
* order for the search to return DirContextAdapter instances.
* @param originalControls
* @return
*/
private static SearchControls buildControls(SearchControls originalControls) {
return new SearchControls(originalControls.getSearchScope(),
originalControls.getCountLimit(), originalControls.getTimeLimit(),
originalControls.getReturningAttributes(), RETURN_OBJECT,
originalControls.getDerefLinkFlag());
}
/**
* Sets the search controls which will be used for search operations by the template.
*
* @param searchControls the SearchControls instance which will be cached in the
* template.
*/
public void setSearchControls(SearchControls searchControls) {
this.searchControls = searchControls;
}
}
@@ -44,110 +44,126 @@ import java.util.*;
* @author Luke Taylor
* @since 3.1
*/
public abstract class AbstractLdapAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
protected final Log logger = LogFactory.getLog(getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private boolean useAuthenticationRequestCredentials = true;
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
protected UserDetailsContextMapper userDetailsContextMapper = new LdapUserDetailsMapper();
public abstract class AbstractLdapAuthenticationProvider implements
AuthenticationProvider, MessageSourceAware {
protected final Log logger = LogFactory.getLog(getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private boolean useAuthenticationRequestCredentials = true;
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
protected UserDetailsContextMapper userDetailsContextMapper = new LdapUserDetailsMapper();
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage("LdapAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage("LdapAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
final UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken)authentication;
final UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) authentication;
String username = userToken.getName();
String password = (String) authentication.getCredentials();
String username = userToken.getName();
String password = (String) authentication.getCredentials();
if (logger.isDebugEnabled()) {
logger.debug("Processing authentication request for user: " + username);
}
if (logger.isDebugEnabled()) {
logger.debug("Processing authentication request for user: " + username);
}
if (!StringUtils.hasLength(username)) {
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
"Empty Username"));
}
if (!StringUtils.hasLength(username)) {
throw new BadCredentialsException(messages.getMessage(
"LdapAuthenticationProvider.emptyUsername", "Empty Username"));
}
if (!StringUtils.hasLength(password)) {
throw new BadCredentialsException(messages.getMessage("AbstractLdapAuthenticationProvider.emptyPassword",
"Empty Password"));
}
if (!StringUtils.hasLength(password)) {
throw new BadCredentialsException(messages.getMessage(
"AbstractLdapAuthenticationProvider.emptyPassword", "Empty Password"));
}
Assert.notNull(password, "Null password was supplied in authentication token");
Assert.notNull(password, "Null password was supplied in authentication token");
DirContextOperations userData = doAuthentication(userToken);
DirContextOperations userData = doAuthentication(userToken);
UserDetails user = userDetailsContextMapper.mapUserFromContext(userData, authentication.getName(),
loadUserAuthorities(userData, authentication.getName(), (String)authentication.getCredentials()));
UserDetails user = userDetailsContextMapper.mapUserFromContext(
userData,
authentication.getName(),
loadUserAuthorities(userData, authentication.getName(),
(String) authentication.getCredentials()));
return createSuccessfulAuthentication(userToken, user);
}
return createSuccessfulAuthentication(userToken, user);
}
protected abstract DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth);
protected abstract DirContextOperations doAuthentication(
UsernamePasswordAuthenticationToken auth);
protected abstract Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password);
protected abstract Collection<? extends GrantedAuthority> loadUserAuthorities(
DirContextOperations userData, String username, String password);
/**
* Creates the final {@code Authentication} object which will be returned from the {@code authenticate} method.
*
* @param authentication the original authentication request token
* @param user the <tt>UserDetails</tt> instance returned by the configured <tt>UserDetailsContextMapper</tt>.
* @return the Authentication object for the fully authenticated user.
*/
protected Authentication createSuccessfulAuthentication(UsernamePasswordAuthenticationToken authentication,
UserDetails user) {
Object password = useAuthenticationRequestCredentials ? authentication.getCredentials() : user.getPassword();
/**
* Creates the final {@code Authentication} object which will be returned from the
* {@code authenticate} method.
*
* @param authentication the original authentication request token
* @param user the <tt>UserDetails</tt> instance returned by the configured
* <tt>UserDetailsContextMapper</tt>.
* @return the Authentication object for the fully authenticated user.
*/
protected Authentication createSuccessfulAuthentication(
UsernamePasswordAuthenticationToken authentication, UserDetails user) {
Object password = useAuthenticationRequestCredentials ? authentication
.getCredentials() : user.getPassword();
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password,
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
user, password, authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
return result;
}
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
/**
* Determines whether the supplied password will be used as the credentials in the successful authentication
* token. If set to false, then the password will be obtained from the UserDetails object
* created by the configured {@code UserDetailsContextMapper}.
* Often it will not be possible to read the password from the directory, so defaults to true.
*
* @param useAuthenticationRequestCredentials
*/
public void setUseAuthenticationRequestCredentials(boolean useAuthenticationRequestCredentials) {
this.useAuthenticationRequestCredentials = useAuthenticationRequestCredentials;
}
/**
* Determines whether the supplied password will be used as the credentials in the
* successful authentication token. If set to false, then the password will be
* obtained from the UserDetails object created by the configured
* {@code UserDetailsContextMapper}. Often it will not be possible to read the
* password from the directory, so defaults to true.
*
* @param useAuthenticationRequestCredentials
*/
public void setUseAuthenticationRequestCredentials(
boolean useAuthenticationRequestCredentials) {
this.useAuthenticationRequestCredentials = useAuthenticationRequestCredentials;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
this.authoritiesMapper = authoritiesMapper;
}
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
this.authoritiesMapper = authoritiesMapper;
}
/**
* Allows a custom strategy to be used for creating the <tt>UserDetails</tt> which will be stored as the principal
* in the <tt>Authentication</tt> returned by the
* {@link #createSuccessfulAuthentication(org.springframework.security.authentication.UsernamePasswordAuthenticationToken, org.springframework.security.core.userdetails.UserDetails)} method.
*
* @param userDetailsContextMapper the strategy instance. If not set, defaults to a simple
* <tt>LdapUserDetailsMapper</tt>.
*/
public void setUserDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper) {
Assert.notNull(userDetailsContextMapper, "UserDetailsContextMapper must not be null");
this.userDetailsContextMapper = userDetailsContextMapper;
}
/**
* Allows a custom strategy to be used for creating the <tt>UserDetails</tt> which
* will be stored as the principal in the <tt>Authentication</tt> returned by the
* {@link #createSuccessfulAuthentication(org.springframework.security.authentication.UsernamePasswordAuthenticationToken, org.springframework.security.core.userdetails.UserDetails)}
* method.
*
* @param userDetailsContextMapper the strategy instance. If not set, defaults to a
* simple <tt>LdapUserDetailsMapper</tt>.
*/
public void setUserDetailsContextMapper(
UserDetailsContextMapper userDetailsContextMapper) {
Assert.notNull(userDetailsContextMapper,
"UserDetailsContextMapper must not be null");
this.userDetailsContextMapper = userDetailsContextMapper;
}
/**
* Provides access to the injected {@code UserDetailsContextMapper} strategy for use by subclasses.
*/
protected UserDetailsContextMapper getUserDetailsContextMapper() {
return userDetailsContextMapper;
}
/**
* Provides access to the injected {@code UserDetailsContextMapper} strategy for use
* by subclasses.
*/
protected UserDetailsContextMapper getUserDetailsContextMapper() {
return userDetailsContextMapper;
}
}
@@ -29,116 +29,130 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Base class for the authenticator implementations.
*
* @author Luke Taylor
*/
public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, InitializingBean, MessageSourceAware {
//~ Instance fields ================================================================================================
public abstract class AbstractLdapAuthenticator implements LdapAuthenticator,
InitializingBean, MessageSourceAware {
// ~ Instance fields
// ================================================================================================
private final ContextSource contextSource;
private final ContextSource contextSource;
/** Optional search object which can be used to locate a user when a simple DN match isn't sufficient */
private LdapUserSearch userSearch;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
/**
* Optional search object which can be used to locate a user when a simple DN match
* isn't sufficient
*/
private LdapUserSearch userSearch;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
/** The attributes which will be retrieved from the directory. Null means all attributes */
private String[] userAttributes = null;
/**
* The attributes which will be retrieved from the directory. Null means all
* attributes
*/
private String[] userAttributes = null;
//private String[] userDnPattern = null;
/** Stores the patterns which are used as potential DN matches */
private MessageFormat[] userDnFormat = null;
// private String[] userDnPattern = null;
/** Stores the patterns which are used as potential DN matches */
private MessageFormat[] userDnFormat = null;
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
/**
* Create an initialized instance with the {@link ContextSource} provided.
*
* @param contextSource
*/
public AbstractLdapAuthenticator(ContextSource contextSource) {
Assert.notNull(contextSource, "contextSource must not be null.");
this.contextSource = contextSource;
}
/**
* Create an initialized instance with the {@link ContextSource} provided.
*
* @param contextSource
*/
public AbstractLdapAuthenticator(ContextSource contextSource) {
Assert.notNull(contextSource, "contextSource must not be null.");
this.contextSource = contextSource;
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.isTrue((userDnFormat != null) || (userSearch != null),
"Either an LdapUserSearch or DN pattern (or both) must be supplied.");
}
public void afterPropertiesSet() throws Exception {
Assert.isTrue((userDnFormat != null) || (userSearch != null),
"Either an LdapUserSearch or DN pattern (or both) must be supplied.");
}
protected ContextSource getContextSource() {
return contextSource;
}
protected ContextSource getContextSource() {
return contextSource;
}
public String[] getUserAttributes() {
return userAttributes;
}
public String[] getUserAttributes() {
return userAttributes;
}
/**
* Builds list of possible DNs for the user, worked out from the <tt>userDnPatterns</tt> property.
*
* @param username the user's login name
*
* @return the list of possible DN matches, empty if <tt>userDnPatterns</tt> wasn't set.
*/
protected List<String> getUserDns(String username) {
if (userDnFormat == null) {
return Collections.emptyList();
}
/**
* Builds list of possible DNs for the user, worked out from the
* <tt>userDnPatterns</tt> property.
*
* @param username the user's login name
*
* @return the list of possible DN matches, empty if <tt>userDnPatterns</tt> wasn't
* set.
*/
protected List<String> getUserDns(String username) {
if (userDnFormat == null) {
return Collections.emptyList();
}
List<String> userDns = new ArrayList<String>(userDnFormat.length);
String[] args = new String[] {LdapEncoder.nameEncode(username)};
List<String> userDns = new ArrayList<String>(userDnFormat.length);
String[] args = new String[] { LdapEncoder.nameEncode(username) };
synchronized (userDnFormat) {
for (MessageFormat formatter : userDnFormat) {
userDns.add(formatter.format(args));
}
}
synchronized (userDnFormat) {
for (MessageFormat formatter : userDnFormat) {
userDns.add(formatter.format(args));
}
}
return userDns;
}
return userDns;
}
protected LdapUserSearch getUserSearch() {
return userSearch;
}
protected LdapUserSearch getUserSearch() {
return userSearch;
}
public void setMessageSource(MessageSource messageSource) {
Assert.notNull("Message source must not be null");
this.messages = new MessageSourceAccessor(messageSource);
}
public void setMessageSource(MessageSource messageSource) {
Assert.notNull("Message source must not be null");
this.messages = new MessageSourceAccessor(messageSource);
}
/**
* Sets the user attributes which will be retrieved from the directory.
*
* @param userAttributes
*/
public void setUserAttributes(String[] userAttributes) {
Assert.notNull(userAttributes, "The userAttributes property cannot be set to null");
this.userAttributes = userAttributes;
}
/**
* Sets the user attributes which will be retrieved from the directory.
*
* @param userAttributes
*/
public void setUserAttributes(String[] userAttributes) {
Assert.notNull(userAttributes,
"The userAttributes property cannot be set to null");
this.userAttributes = userAttributes;
}
/**
* Sets the pattern which will be used to supply a DN for the user. The pattern should be the name relative
* to the root DN. The pattern argument {0} will contain the username. An example would be "cn={0},ou=people".
*
* @param dnPattern the array of patterns which will be tried when converting a username to a DN.
*/
public void setUserDnPatterns(String[] dnPattern) {
Assert.notNull(dnPattern, "The array of DN patterns cannot be set to null");
// this.userDnPattern = dnPattern;
userDnFormat = new MessageFormat[dnPattern.length];
/**
* Sets the pattern which will be used to supply a DN for the user. The pattern should
* be the name relative to the root DN. The pattern argument {0} will contain the
* username. An example would be "cn={0},ou=people".
*
* @param dnPattern the array of patterns which will be tried when converting a
* username to a DN.
*/
public void setUserDnPatterns(String[] dnPattern) {
Assert.notNull(dnPattern, "The array of DN patterns cannot be set to null");
// this.userDnPattern = dnPattern;
userDnFormat = new MessageFormat[dnPattern.length];
for (int i = 0; i < dnPattern.length; i++) {
userDnFormat[i] = new MessageFormat(dnPattern[i]);
}
}
for (int i = 0; i < dnPattern.length; i++) {
userDnFormat[i] = new MessageFormat(dnPattern[i]);
}
}
public void setUserSearch(LdapUserSearch userSearch) {
Assert.notNull(userSearch, "The userSearch cannot be set to null");
this.userSearch = userSearch;
}
public void setUserSearch(LdapUserSearch userSearch) {
Assert.notNull(userSearch, "The userSearch cannot be set to null");
this.userSearch = userSearch;
}
}
@@ -34,7 +34,6 @@ import org.springframework.security.ldap.ppolicy.PasswordPolicyControlExtractor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An authenticator which binds as a user.
*
@@ -43,113 +42,126 @@ import org.springframework.util.StringUtils;
* @see AbstractLdapAuthenticator
*/
public class BindAuthenticator extends AbstractLdapAuthenticator {
//~ Static fields/initializers =====================================================================================
// ~ Static fields/initializers
// =====================================================================================
private static final Log logger = LogFactory.getLog(BindAuthenticator.class);
private static final Log logger = LogFactory.getLog(BindAuthenticator.class);
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
/**
* Create an initialized instance using the {@link BaseLdapPathContextSource} provided.
*
* @param contextSource the BaseLdapPathContextSource instance against which bind operations will be
* performed.
*
*/
public BindAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
/**
* Create an initialized instance using the {@link BaseLdapPathContextSource}
* provided.
*
* @param contextSource the BaseLdapPathContextSource instance against which bind
* operations will be performed.
*
*/
public BindAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
public DirContextOperations authenticate(Authentication authentication) {
DirContextOperations user = null;
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects");
public DirContextOperations authenticate(Authentication authentication) {
DirContextOperations user = null;
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects");
String username = authentication.getName();
String password = (String)authentication.getCredentials();
String username = authentication.getName();
String password = (String) authentication.getCredentials();
if (!StringUtils.hasLength(password)) {
logger.debug("Rejecting empty password for user " + username);
throw new BadCredentialsException(messages.getMessage("BindAuthenticator.emptyPassword",
"Empty Password"));
}
if (!StringUtils.hasLength(password)) {
logger.debug("Rejecting empty password for user " + username);
throw new BadCredentialsException(messages.getMessage(
"BindAuthenticator.emptyPassword", "Empty Password"));
}
// If DN patterns are configured, try authenticating with them directly
for (String dn : getUserDns(username)) {
user = bindWithDn(dn, username, password);
// If DN patterns are configured, try authenticating with them directly
for (String dn : getUserDns(username)) {
user = bindWithDn(dn, username, password);
if (user != null) {
break;
}
}
if (user != null) {
break;
}
}
// Otherwise use the configured search object to find the user and authenticate with the returned DN.
if (user == null && getUserSearch() != null) {
DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
user = bindWithDn(userFromSearch.getDn().toString(), username, password);
}
// Otherwise use the configured search object to find the user and authenticate
// with the returned DN.
if (user == null && getUserSearch() != null) {
DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
user = bindWithDn(userFromSearch.getDn().toString(), username, password);
}
if (user == null) {
throw new BadCredentialsException(
messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
}
if (user == null) {
throw new BadCredentialsException(messages.getMessage(
"BindAuthenticator.badCredentials", "Bad credentials"));
}
return user;
}
return user;
}
private DirContextOperations bindWithDn(String userDnStr, String username, String password) {
BaseLdapPathContextSource ctxSource = (BaseLdapPathContextSource) getContextSource();
DistinguishedName userDn = new DistinguishedName(userDnStr);
DistinguishedName fullDn = new DistinguishedName(userDn);
fullDn.prepend(ctxSource.getBaseLdapPath());
private DirContextOperations bindWithDn(String userDnStr, String username,
String password) {
BaseLdapPathContextSource ctxSource = (BaseLdapPathContextSource) getContextSource();
DistinguishedName userDn = new DistinguishedName(userDnStr);
DistinguishedName fullDn = new DistinguishedName(userDn);
fullDn.prepend(ctxSource.getBaseLdapPath());
logger.debug("Attempting to bind as " + fullDn);
logger.debug("Attempting to bind as " + fullDn);
DirContext ctx = null;
try {
ctx = getContextSource().getContext(fullDn.toString(), password);
// Check for password policy control
PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor.extractControl(ctx);
DirContext ctx = null;
try {
ctx = getContextSource().getContext(fullDn.toString(), password);
// Check for password policy control
PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor
.extractControl(ctx);
logger.debug("Retrieving attributes...");
logger.debug("Retrieving attributes...");
Attributes attrs = ctx.getAttributes(userDn, getUserAttributes());
Attributes attrs = ctx.getAttributes(userDn, getUserAttributes());
DirContextAdapter result = new DirContextAdapter(attrs, userDn, ctxSource.getBaseLdapPath());
DirContextAdapter result = new DirContextAdapter(attrs, userDn,
ctxSource.getBaseLdapPath());
if (ppolicy != null) {
result.setAttributeValue(ppolicy.getID(), ppolicy);
}
if (ppolicy != null) {
result.setAttributeValue(ppolicy.getID(), ppolicy);
}
return result;
} catch (NamingException e) {
// This will be thrown if an invalid user name is used and the method may
// be called multiple times to try different names, so we trap the exception
// unless a subclass wishes to implement more specialized behaviour.
if ((e instanceof org.springframework.ldap.AuthenticationException)
|| (e instanceof org.springframework.ldap.OperationNotSupportedException)) {
handleBindException(userDnStr, username, e);
} else {
throw e;
}
} catch (javax.naming.NamingException e) {
throw LdapUtils.convertLdapException(e);
} finally {
LdapUtils.closeContext(ctx);
}
return result;
}
catch (NamingException e) {
// This will be thrown if an invalid user name is used and the method may
// be called multiple times to try different names, so we trap the exception
// unless a subclass wishes to implement more specialized behaviour.
if ((e instanceof org.springframework.ldap.AuthenticationException)
|| (e instanceof org.springframework.ldap.OperationNotSupportedException)) {
handleBindException(userDnStr, username, e);
}
else {
throw e;
}
}
catch (javax.naming.NamingException e) {
throw LdapUtils.convertLdapException(e);
}
finally {
LdapUtils.closeContext(ctx);
}
return null;
}
return null;
}
/**
* Allows subclasses to inspect the exception thrown by an attempt to bind with a particular DN.
* The default implementation just reports the failure to the debug logger.
*/
protected void handleBindException(String userDn, String username, Throwable cause) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to bind as " + userDn + ": " + cause);
}
}
/**
* Allows subclasses to inspect the exception thrown by an attempt to bind with a
* particular DN. The default implementation just reports the failure to the debug
* logger.
*/
protected void handleBindException(String userDn, String username, Throwable cause) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to bind as " + userDn + ": " + cause);
}
}
}
@@ -32,41 +32,43 @@ import org.springframework.util.Assert;
import java.util.*;
/**
* An {@link org.springframework.security.authentication.AuthenticationProvider} implementation that authenticates
* against an LDAP server.
* An {@link org.springframework.security.authentication.AuthenticationProvider}
* implementation that authenticates against an LDAP server.
* <p>
* There are many ways in which an LDAP directory can be configured so this class delegates most of
* its responsibilities to two separate strategy interfaces, {@link LdapAuthenticator}
* and {@link LdapAuthoritiesPopulator}.
* There are many ways in which an LDAP directory can be configured so this class
* delegates most of its responsibilities to two separate strategy interfaces,
* {@link LdapAuthenticator} and {@link LdapAuthoritiesPopulator}.
*
* <h3>LdapAuthenticator</h3>
* This interface is responsible for performing the user authentication and retrieving
* the user's information from the directory. Example implementations are {@link
* org.springframework.security.ldap.authentication.BindAuthenticator BindAuthenticator} which authenticates
* the user by "binding" as that user, and
* {@link org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator PasswordComparisonAuthenticator}
* which compares the supplied password with the value stored in the directory, using an LDAP "compare"
* operation.
* This interface is responsible for performing the user authentication and retrieving the
* user's information from the directory. Example implementations are
* {@link org.springframework.security.ldap.authentication.BindAuthenticator
* BindAuthenticator} which authenticates the user by "binding" as that user, and
* {@link org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator
* PasswordComparisonAuthenticator} which compares the supplied password with the value
* stored in the directory, using an LDAP "compare" operation.
* <p>
* The task of retrieving the user attributes is delegated to the authenticator because the permissions on the
* attributes may depend on the type of authentication being used; for example, if binding as the user, it may be
* necessary to read them with the user's own permissions (using the same context used for the bind operation).
* The task of retrieving the user attributes is delegated to the authenticator because
* the permissions on the attributes may depend on the type of authentication being used;
* for example, if binding as the user, it may be necessary to read them with the user's
* own permissions (using the same context used for the bind operation).
*
* <h3>LdapAuthoritiesPopulator</h3>
* Once the user has been authenticated, this interface is called to obtain the set of granted authorities for the
* user.
* The {@link DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator}
* can be configured to obtain user role information from the user's attributes and/or to perform a search for
* "groups" that the user is a member of and map these to roles.
* Once the user has been authenticated, this interface is called to obtain the set of
* granted authorities for the user. The {@link DefaultLdapAuthoritiesPopulator
* DefaultLdapAuthoritiesPopulator} can be configured to obtain user role information from
* the user's attributes and/or to perform a search for "groups" that the user is a member
* of and map these to roles.
*
* <p>
* A custom implementation could obtain the roles from a completely different source, for example from a database.
* A custom implementation could obtain the roles from a completely different source, for
* example from a database.
*
* <h3>Configuration</h3>
*
* A simple configuration might be as follows:
*
* <pre>
* &lt;bean id=&quot;contextSource&quot;
* class=&quot;org.springframework.security.ldap.DefaultSpringSecurityContextSource&quot;&gt;
@@ -74,7 +76,7 @@ import java.util.*;
* &lt;property name=&quot;userDn&quot; value=&quot;cn=manager,dc=springframework,dc=org&quot;/&gt;
* &lt;property name=&quot;password&quot; value=&quot;password&quot;/&gt;
* &lt;/bean&gt;
*
*
* &lt;bean id=&quot;ldapAuthProvider&quot;
* class=&quot;org.springframework.security.ldap.authentication.LdapAuthenticationProvider&quot;&gt;
* &lt;constructor-arg&gt;
@@ -91,22 +93,24 @@ import java.util.*;
* &lt;/bean&gt;
* &lt;/constructor-arg&gt;
* &lt;/bean&gt;
*</pre>
* </pre>
*
* <p>
* This would set up the provider to access an LDAP server with URL
* <tt>ldap://monkeymachine:389/dc=springframework,dc=org</tt>. Authentication will be performed by attempting to bind
* with the DN <tt>uid=&lt;user-login-name&gt;,ou=people,dc=springframework,dc=org</tt>. After successful
* authentication, roles will be assigned to the user by searching under the DN
* <tt>ou=groups,dc=springframework,dc=org</tt> with the default filter <tt>(member=&lt;user's-DN&gt;)</tt>. The role
* name will be taken from the "ou" attribute of each match.
* <tt>ldap://monkeymachine:389/dc=springframework,dc=org</tt>. Authentication will be
* performed by attempting to bind with the DN
* <tt>uid=&lt;user-login-name&gt;,ou=people,dc=springframework,dc=org</tt>. After
* successful authentication, roles will be assigned to the user by searching under the DN
* <tt>ou=groups,dc=springframework,dc=org</tt> with the default filter
* <tt>(member=&lt;user's-DN&gt;)</tt>. The role name will be taken from the "ou"
* attribute of each match.
* <p>
* The authenticate method will reject empty passwords outright. LDAP servers may allow an anonymous
* bind operation with an empty password, even if a DN is supplied. In practice this means that if
* the LDAP directory is configured to allow unauthenticated access, it might be possible to
* authenticate as <i>any</i> user just by supplying an empty password.
* More information on the misuse of unauthenticated access can be found in
* <a href="http://www.ietf.org/internet-drafts/draft-ietf-ldapbis-authmeth-19.txt">
* The authenticate method will reject empty passwords outright. LDAP servers may allow an
* anonymous bind operation with an empty password, even if a DN is supplied. In practice
* this means that if the LDAP directory is configured to allow unauthenticated access, it
* might be possible to authenticate as <i>any</i> user just by supplying an empty
* password. More information on the misuse of unauthenticated access can be found in <a
* href="http://www.ietf.org/internet-drafts/draft-ietf-ldapbis-authmeth-19.txt">
* draft-ietf-ldapbis-authmeth-19.txt</a>.
*
*
@@ -116,86 +120,99 @@ import java.util.*;
* @see DefaultLdapAuthoritiesPopulator
*/
public class LdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private LdapAuthenticator authenticator;
private LdapAuthoritiesPopulator authoritiesPopulator;
private boolean hideUserNotFoundExceptions = true;
private LdapAuthenticator authenticator;
private LdapAuthoritiesPopulator authoritiesPopulator;
private boolean hideUserNotFoundExceptions = true;
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
/**
* Create an instance with the supplied authenticator and authorities populator implementations.
*
* @param authenticator the authentication strategy (bind, password comparison, etc)
* to be used by this provider for authenticating users.
* @param authoritiesPopulator the strategy for obtaining the authorities for a given user after they've been
* authenticated.
*/
public LdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
this.setAuthenticator(authenticator);
this.setAuthoritiesPopulator(authoritiesPopulator);
}
/**
* Create an instance with the supplied authenticator and authorities populator
* implementations.
*
* @param authenticator the authentication strategy (bind, password comparison, etc)
* to be used by this provider for authenticating users.
* @param authoritiesPopulator the strategy for obtaining the authorities for a given
* user after they've been authenticated.
*/
public LdapAuthenticationProvider(LdapAuthenticator authenticator,
LdapAuthoritiesPopulator authoritiesPopulator) {
this.setAuthenticator(authenticator);
this.setAuthoritiesPopulator(authoritiesPopulator);
}
/**
* Creates an instance with the supplied authenticator and a null authorities populator.
* In this case, the authorities must be mapped from the user context.
*
* @param authenticator the authenticator strategy.
*/
public LdapAuthenticationProvider(LdapAuthenticator authenticator) {
this.setAuthenticator(authenticator);
this.setAuthoritiesPopulator(new NullLdapAuthoritiesPopulator());
}
/**
* Creates an instance with the supplied authenticator and a null authorities
* populator. In this case, the authorities must be mapped from the user context.
*
* @param authenticator the authenticator strategy.
*/
public LdapAuthenticationProvider(LdapAuthenticator authenticator) {
this.setAuthenticator(authenticator);
this.setAuthoritiesPopulator(new NullLdapAuthoritiesPopulator());
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
private void setAuthenticator(LdapAuthenticator authenticator) {
Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
this.authenticator = authenticator;
}
private void setAuthenticator(LdapAuthenticator authenticator) {
Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
this.authenticator = authenticator;
}
private LdapAuthenticator getAuthenticator() {
return authenticator;
}
private LdapAuthenticator getAuthenticator() {
return authenticator;
}
private void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) {
Assert.notNull(authoritiesPopulator, "An LdapAuthoritiesPopulator must be supplied");
this.authoritiesPopulator = authoritiesPopulator;
}
private void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) {
Assert.notNull(authoritiesPopulator,
"An LdapAuthoritiesPopulator must be supplied");
this.authoritiesPopulator = authoritiesPopulator;
}
protected LdapAuthoritiesPopulator getAuthoritiesPopulator() {
return authoritiesPopulator;
}
protected LdapAuthoritiesPopulator getAuthoritiesPopulator() {
return authoritiesPopulator;
}
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}
@Override
protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken authentication) {
try {
return getAuthenticator().authenticate(authentication);
} catch (PasswordPolicyException ppe) {
// The only reason a ppolicy exception can occur during a bind is that the account is locked.
throw new LockedException(messages.getMessage(ppe.getStatus().getErrorCode(),
ppe.getStatus().getDefaultMessage()));
} catch (UsernameNotFoundException notFound) {
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"LdapAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
throw notFound;
}
} catch (NamingException ldapAccessFailure) {
throw new InternalAuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
}
}
@Override
protected DirContextOperations doAuthentication(
UsernamePasswordAuthenticationToken authentication) {
try {
return getAuthenticator().authenticate(authentication);
}
catch (PasswordPolicyException ppe) {
// The only reason a ppolicy exception can occur during a bind is that the
// account is locked.
throw new LockedException(messages.getMessage(ppe.getStatus().getErrorCode(),
ppe.getStatus().getDefaultMessage()));
}
catch (UsernameNotFoundException notFound) {
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"LdapAuthenticationProvider.badCredentials", "Bad credentials"));
}
else {
throw notFound;
}
}
catch (NamingException ldapAccessFailure) {
throw new InternalAuthenticationServiceException(
ldapAccessFailure.getMessage(), ldapAccessFailure);
}
}
@Override
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) {
return getAuthoritiesPopulator().getGrantedAuthorities(userData, username);
}
@Override
protected Collection<? extends GrantedAuthority> loadUserAuthorities(
DirContextOperations userData, String username, String password) {
return getAuthoritiesPopulator().getGrantedAuthorities(userData, username);
}
}
@@ -18,12 +18,11 @@ package org.springframework.security.ldap.authentication;
import org.springframework.security.core.Authentication;
import org.springframework.ldap.core.DirContextOperations;
/**
* The strategy interface for locating and authenticating an Ldap user.
* <p>
* The LdapAuthenticationProvider calls this interface to authenticate a user
* and obtain the information for that user from the directory.
* The LdapAuthenticationProvider calls this interface to authenticate a user and obtain
* the information for that user from the directory.
*
* @author Luke Taylor
*
@@ -31,13 +30,14 @@ import org.springframework.ldap.core.DirContextOperations;
* @see org.springframework.security.ldap.authentication.UserDetailsServiceLdapAuthoritiesPopulator
*/
public interface LdapAuthenticator {
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* Authenticates as a user and obtains additional user information from the directory.
*
* @param authentication
* @return the details of the successfully authenticated user.
*/
DirContextOperations authenticate(Authentication authentication);
/**
* Authenticates as a user and obtains additional user information from the directory.
*
* @param authentication
* @return the details of the successfully authenticated user.
*/
DirContextOperations authenticate(Authentication authentication);
}
@@ -22,8 +22,8 @@ import org.springframework.ldap.BadLdapGrammarException;
* Helper class to encode and decode ldap names and values.
*
* <p>
* NOTE: This is a copy from Spring LDAP so that both Spring LDAP 1.x and 2.x
* can be supported without reflection.
* NOTE: This is a copy from Spring LDAP so that both Spring LDAP 1.x and 2.x can be
* supported without reflection.
* </p>
*
* @author Adam Skogman
@@ -31,207 +31,211 @@ import org.springframework.ldap.BadLdapGrammarException;
*/
final class LdapEncoder {
private static final int HEX = 16;
private static String[] NAME_ESCAPE_TABLE = new String[96];
private static final int HEX = 16;
private static String[] NAME_ESCAPE_TABLE = new String[96];
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
static {
static {
// Name encoding table -------------------------------------
// Name encoding table -------------------------------------
// all below 0x20 (control chars)
for (char c = 0; c < ' '; c++) {
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
}
// all below 0x20 (control chars)
for (char c = 0; c < ' '; c++) {
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
}
NAME_ESCAPE_TABLE['#'] = "\\#";
NAME_ESCAPE_TABLE[','] = "\\,";
NAME_ESCAPE_TABLE[';'] = "\\;";
NAME_ESCAPE_TABLE['='] = "\\=";
NAME_ESCAPE_TABLE['+'] = "\\+";
NAME_ESCAPE_TABLE['<'] = "\\<";
NAME_ESCAPE_TABLE['>'] = "\\>";
NAME_ESCAPE_TABLE['\"'] = "\\\"";
NAME_ESCAPE_TABLE['\\'] = "\\\\";
NAME_ESCAPE_TABLE['#'] = "\\#";
NAME_ESCAPE_TABLE[','] = "\\,";
NAME_ESCAPE_TABLE[';'] = "\\;";
NAME_ESCAPE_TABLE['='] = "\\=";
NAME_ESCAPE_TABLE['+'] = "\\+";
NAME_ESCAPE_TABLE['<'] = "\\<";
NAME_ESCAPE_TABLE['>'] = "\\>";
NAME_ESCAPE_TABLE['\"'] = "\\\"";
NAME_ESCAPE_TABLE['\\'] = "\\\\";
// Filter encoding table -------------------------------------
// Filter encoding table -------------------------------------
// fill with char itself
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
}
// fill with char itself
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
}
// escapes (RFC2254)
FILTER_ESCAPE_TABLE['*'] = "\\2a";
FILTER_ESCAPE_TABLE['('] = "\\28";
FILTER_ESCAPE_TABLE[')'] = "\\29";
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
FILTER_ESCAPE_TABLE[0] = "\\00";
// escapes (RFC2254)
FILTER_ESCAPE_TABLE['*'] = "\\2a";
FILTER_ESCAPE_TABLE['('] = "\\28";
FILTER_ESCAPE_TABLE[')'] = "\\29";
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
FILTER_ESCAPE_TABLE[0] = "\\00";
}
}
/**
* All static methods - not to be instantiated.
*/
private LdapEncoder() {
}
/**
* All static methods - not to be instantiated.
*/
private LdapEncoder() {
}
protected static String toTwoCharHex(char c) {
protected static String toTwoCharHex(char c) {
String raw = Integer.toHexString(c).toUpperCase();
String raw = Integer.toHexString(c).toUpperCase();
if (raw.length() > 1) {
return raw;
} else {
return "0" + raw;
}
}
if (raw.length() > 1) {
return raw;
}
else {
return "0" + raw;
}
}
/**
* Escape a value for use in a filter.
*
* @param value
* the value to escape.
* @return a properly escaped representation of the supplied value.
*/
public static String filterEncode(String value) {
/**
* Escape a value for use in a filter.
*
* @param value the value to escape.
* @return a properly escaped representation of the supplied value.
*/
public static String filterEncode(String value) {
if (value == null)
return null;
if (value == null)
return null;
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length();
int length = value.length();
for (int i = 0; i < length; i++) {
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
char c = value.charAt(i);
if (c < FILTER_ESCAPE_TABLE.length) {
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
} else {
// default: add the char
encodedValue.append(c);
}
}
if (c < FILTER_ESCAPE_TABLE.length) {
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
}
else {
// default: add the char
encodedValue.append(c);
}
}
return encodedValue.toString();
}
return encodedValue.toString();
}
/**
* LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
*
* <br/>Escapes:<br/> ' ' [space] - "\ " [if first or last] <br/> '#'
* [hash] - "\#" <br/> ',' [comma] - "\," <br/> ';' [semicolon] - "\;" <br/> '=
* [equals] - "\=" <br/> '+' [plus] - "\+" <br/> '&lt;' [less than] -
* "\&lt;" <br/> '&gt;' [greater than] - "\&gt;" <br/> '"' [double quote] -
* "\"" <br/> '\' [backslash] - "\\" <br/>
*
* @param value
* the value to escape.
* @return The escaped value.
*/
public static String nameEncode(String value) {
/**
* LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
*
* <br/>
* Escapes:<br/>
* ' ' [space] - "\ " [if first or last] <br/>
* '#' [hash] - "\#" <br/>
* ',' [comma] - "\," <br/>
* ';' [semicolon] - "\;" <br/>
* '= [equals] - "\=" <br/>
* '+' [plus] - "\+" <br/>
* '&lt;' [less than] - "\&lt;" <br/>
* '&gt;' [greater than] - "\&gt;" <br/>
* '"' [double quote] - "\"" <br/>
* '\' [backslash] - "\\" <br/>
*
* @param value the value to escape.
* @return The escaped value.
*/
public static String nameEncode(String value) {
if (value == null)
return null;
if (value == null)
return null;
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length();
int last = length - 1;
int length = value.length();
int last = length - 1;
for (int i = 0; i < length; i++) {
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
char c = value.charAt(i);
// space first or last
if (c == ' ' && (i == 0 || i == last)) {
encodedValue.append("\\ ");
continue;
}
// space first or last
if (c == ' ' && (i == 0 || i == last)) {
encodedValue.append("\\ ");
continue;
}
if (c < NAME_ESCAPE_TABLE.length) {
// check in table for escapes
String esc = NAME_ESCAPE_TABLE[c];
if (c < NAME_ESCAPE_TABLE.length) {
// check in table for escapes
String esc = NAME_ESCAPE_TABLE[c];
if (esc != null) {
encodedValue.append(esc);
continue;
}
}
if (esc != null) {
encodedValue.append(esc);
continue;
}
}
// default: add the char
encodedValue.append(c);
}
// default: add the char
encodedValue.append(c);
}
return encodedValue.toString();
return encodedValue.toString();
}
}
/**
* Decodes a value. Converts escaped chars to ordinary chars.
*
* @param value
* Trimmed value, so no leading an trailing blanks, except an
* escaped space last.
* @return The decoded value as a string.
* @throws BadLdapGrammarException
*/
static public String nameDecode(String value)
throws BadLdapGrammarException {
/**
* Decodes a value. Converts escaped chars to ordinary chars.
*
* @param value Trimmed value, so no leading an trailing blanks, except an escaped
* space last.
* @return The decoded value as a string.
* @throws BadLdapGrammarException
*/
static public String nameDecode(String value) throws BadLdapGrammarException {
if (value == null)
return null;
if (value == null)
return null;
// make buffer same size
StringBuilder decoded = new StringBuilder(value.length());
// make buffer same size
StringBuilder decoded = new StringBuilder(value.length());
int i = 0;
while (i < value.length()) {
char currentChar = value.charAt(i);
if (currentChar == '\\') {
if (value.length() <= i + 1) {
// Ending with a single backslash is not allowed
throw new BadLdapGrammarException(
"Unexpected end of value " + "unterminated '\\'");
} else {
char nextChar = value.charAt(i + 1);
if (nextChar == ',' || nextChar == '=' || nextChar == '+'
|| nextChar == '<' || nextChar == '>'
|| nextChar == '#' || nextChar == ';'
|| nextChar == '\\' || nextChar == '\"'
|| nextChar == ' ') {
// Normal backslash escape
decoded.append(nextChar);
i += 2;
} else {
if (value.length() <= i + 2) {
throw new BadLdapGrammarException(
"Unexpected end of value "
+ "expected special or hex, found '"
+ nextChar + "'");
} else {
// This should be a hex value
String hexString = "" + nextChar
+ value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString,
HEX));
i += 3;
}
}
}
} else {
// This character wasn't escaped - just append it
decoded.append(currentChar);
i++;
}
}
int i = 0;
while (i < value.length()) {
char currentChar = value.charAt(i);
if (currentChar == '\\') {
if (value.length() <= i + 1) {
// Ending with a single backslash is not allowed
throw new BadLdapGrammarException("Unexpected end of value "
+ "unterminated '\\'");
}
else {
char nextChar = value.charAt(i + 1);
if (nextChar == ',' || nextChar == '=' || nextChar == '+'
|| nextChar == '<' || nextChar == '>' || nextChar == '#'
|| nextChar == ';' || nextChar == '\\' || nextChar == '\"'
|| nextChar == ' ') {
// Normal backslash escape
decoded.append(nextChar);
i += 2;
}
else {
if (value.length() <= i + 2) {
throw new BadLdapGrammarException("Unexpected end of value "
+ "expected special or hex, found '" + nextChar + "'");
}
else {
// This should be a hex value
String hexString = "" + nextChar + value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString, HEX));
i += 3;
}
}
}
}
else {
// This character wasn't escaped - just append it
decoded.append(currentChar);
i++;
}
}
return decoded.toString();
return decoded.toString();
}
}
}
@@ -13,7 +13,8 @@ import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
* @since 3.0
*/
public final class NullLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
public Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations userDetails, String username) {
return AuthorityUtils.NO_AUTHORITIES;
}
public Collection<GrantedAuthority> getGrantedAuthorities(
DirContextOperations userDetails, String username) {
return AuthorityUtils.NO_AUTHORITIES;
}
}
@@ -31,132 +31,145 @@ import org.springframework.security.crypto.codec.Utf8;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.util.Assert;
/**
* An {@link org.springframework.security.ldap.authentication.LdapAuthenticator LdapAuthenticator} which compares the login
* password with the value stored in the directory using a remote LDAP "compare" operation.
* An {@link org.springframework.security.ldap.authentication.LdapAuthenticator
* LdapAuthenticator} which compares the login password with the value stored in the
* directory using a remote LDAP "compare" operation.
*
* <p>
* If passwords are stored in digest form in the repository, then a suitable {@link PasswordEncoder}
* implementation must be supplied. By default, passwords are encoded using the {@link LdapShaPasswordEncoder}.
* Note that compare operations will not work if salted-SHA (SSHA) passwords are used, as it is not possible to
* know the salt value which is a random byte sequence generated by the directory.
* If passwords are stored in digest form in the repository, then a suitable
* {@link PasswordEncoder} implementation must be supplied. By default, passwords are
* encoded using the {@link LdapShaPasswordEncoder}. Note that compare operations will not
* work if salted-SHA (SSHA) passwords are used, as it is not possible to know the salt
* value which is a random byte sequence generated by the directory.
*
* @author Luke Taylor
*/
public final class PasswordComparisonAuthenticator extends AbstractLdapAuthenticator {
//~ Static fields/initializers =====================================================================================
// ~ Static fields/initializers
// =====================================================================================
private static final Log logger = LogFactory.getLog(PasswordComparisonAuthenticator.class);
private static final Log logger = LogFactory
.getLog(PasswordComparisonAuthenticator.class);
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private PasswordEncoder passwordEncoder = new LdapShaPasswordEncoder();
private String passwordAttributeName = "userPassword";
private boolean usePasswordAttrCompare = false;
private PasswordEncoder passwordEncoder = new LdapShaPasswordEncoder();
private String passwordAttributeName = "userPassword";
private boolean usePasswordAttrCompare = false;
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
public PasswordComparisonAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
public PasswordComparisonAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
public DirContextOperations authenticate(final Authentication authentication) {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects");
// locate the user and check the password
public DirContextOperations authenticate(final Authentication authentication) {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects");
// locate the user and check the password
DirContextOperations user = null;
String username = authentication.getName();
String password = (String)authentication.getCredentials();
DirContextOperations user = null;
String username = authentication.getName();
String password = (String) authentication.getCredentials();
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getContextSource());
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(
getContextSource());
for (String userDn : getUserDns(username)) {
try {
user = ldapTemplate.retrieveEntry(userDn, getUserAttributes());
} catch (NameNotFoundException ignore) {
}
if (user != null) {
break;
}
}
for (String userDn : getUserDns(username)) {
try {
user = ldapTemplate.retrieveEntry(userDn, getUserAttributes());
}
catch (NameNotFoundException ignore) {
}
if (user != null) {
break;
}
}
if (user == null && getUserSearch() != null) {
user = getUserSearch().searchForUser(username);
}
if (user == null && getUserSearch() != null) {
user = getUserSearch().searchForUser(username);
}
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username);
}
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username);
}
if (logger.isDebugEnabled()) {
logger.debug("Performing LDAP compare of password attribute '" + passwordAttributeName + "' for user '" +
user.getDn() +"'");
}
if (logger.isDebugEnabled()) {
logger.debug("Performing LDAP compare of password attribute '"
+ passwordAttributeName + "' for user '" + user.getDn() + "'");
}
if (usePasswordAttrCompare && isPasswordAttrCompare(user, password)) {
return user;
} else if(isLdapPasswordCompare(user, ldapTemplate, password)) {
return user;
}
throw new BadCredentialsException(messages.getMessage("PasswordComparisonAuthenticator.badCredentials",
"Bad credentials"));
}
if (usePasswordAttrCompare && isPasswordAttrCompare(user, password)) {
return user;
}
else if (isLdapPasswordCompare(user, ldapTemplate, password)) {
return user;
}
throw new BadCredentialsException(messages.getMessage(
"PasswordComparisonAuthenticator.badCredentials", "Bad credentials"));
}
private boolean isPasswordAttrCompare(DirContextOperations user, String password) {
Object passwordAttrValue = user.getObjectAttribute(passwordAttributeName);
return passwordEncoder.isPasswordValid(new String((byte[])passwordAttrValue), password, null);
}
private boolean isPasswordAttrCompare(DirContextOperations user, String password) {
Object passwordAttrValue = user.getObjectAttribute(passwordAttributeName);
return passwordEncoder.isPasswordValid(new String((byte[]) passwordAttrValue),
password, null);
}
private boolean isLdapPasswordCompare(DirContextOperations user,
SpringSecurityLdapTemplate ldapTemplate, String password) {
String encodedPassword = passwordEncoder.encodePassword(password, null);
byte[] passwordBytes = Utf8.encode(encodedPassword);
return ldapTemplate.compare(user.getDn().toString(), passwordAttributeName, passwordBytes);
}
private boolean isLdapPasswordCompare(DirContextOperations user,
SpringSecurityLdapTemplate ldapTemplate, String password) {
String encodedPassword = passwordEncoder.encodePassword(password, null);
byte[] passwordBytes = Utf8.encode(encodedPassword);
return ldapTemplate.compare(user.getDn().toString(), passwordAttributeName,
passwordBytes);
}
public void setPasswordAttributeName(String passwordAttribute) {
Assert.hasLength(passwordAttribute, "passwordAttributeName must not be empty or null");
this.passwordAttributeName = passwordAttribute;
}
public void setPasswordAttributeName(String passwordAttribute) {
Assert.hasLength(passwordAttribute,
"passwordAttributeName must not be empty or null");
this.passwordAttributeName = passwordAttribute;
}
private void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder must not be null.");
this.passwordEncoder = passwordEncoder;
}
private void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder must not be null.");
this.passwordEncoder = passwordEncoder;
}
public void setPasswordEncoder(Object passwordEncoder) {
if (passwordEncoder instanceof PasswordEncoder) {
this.usePasswordAttrCompare = false;
setPasswordEncoder((PasswordEncoder) passwordEncoder);
return;
}
public void setPasswordEncoder(Object passwordEncoder) {
if (passwordEncoder instanceof PasswordEncoder) {
this.usePasswordAttrCompare = false;
setPasswordEncoder((PasswordEncoder) passwordEncoder);
return;
}
if (passwordEncoder instanceof org.springframework.security.crypto.password.PasswordEncoder) {
final org.springframework.security.crypto.password.PasswordEncoder delegate =
(org.springframework.security.crypto.password.PasswordEncoder)passwordEncoder;
setPasswordEncoder(new PasswordEncoder() {
public String encodePassword(String rawPass, Object salt) {
checkSalt(salt);
return delegate.encode(rawPass);
}
if (passwordEncoder instanceof org.springframework.security.crypto.password.PasswordEncoder) {
final org.springframework.security.crypto.password.PasswordEncoder delegate = (org.springframework.security.crypto.password.PasswordEncoder) passwordEncoder;
setPasswordEncoder(new PasswordEncoder() {
public String encodePassword(String rawPass, Object salt) {
checkSalt(salt);
return delegate.encode(rawPass);
}
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
checkSalt(salt);
return delegate.matches(rawPass, encPass);
}
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
checkSalt(salt);
return delegate.matches(rawPass, encPass);
}
private void checkSalt(Object salt) {
Assert.isNull(salt, "Salt value must be null when used with crypto module PasswordEncoder");
}
});
this.usePasswordAttrCompare = true;
return;
}
private void checkSalt(Object salt) {
Assert.isNull(salt,
"Salt value must be null when used with crypto module PasswordEncoder");
}
});
this.usePasswordAttrCompare = true;
return;
}
throw new IllegalArgumentException("passwordEncoder must be a PasswordEncoder instance");
}
throw new IllegalArgumentException(
"passwordEncoder must be a PasswordEncoder instance");
}
}
@@ -10,59 +10,65 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* An AuthenticationSource to retrieve authentication information stored in Spring Security's
* {@link SecurityContextHolder}.
* An AuthenticationSource to retrieve authentication information stored in Spring
* Security's {@link SecurityContextHolder}.
* <p>
* This is a copy of Spring LDAP's AcegiAuthenticationSource, updated for use with Spring Security 2.0.
* This is a copy of Spring LDAP's AcegiAuthenticationSource, updated for use with Spring
* Security 2.0.
*
* @author Mattias Arthursson
* @author Luke Taylor
* @since 2.0
*/
public class SpringSecurityAuthenticationSource implements AuthenticationSource {
private static final Log log = LogFactory.getLog(SpringSecurityAuthenticationSource.class);
private static final Log log = LogFactory
.getLog(SpringSecurityAuthenticationSource.class);
/**
* Get the principals of the logged in user, in this case the distinguished
* name.
*
* @return the distinguished name of the logged in user.
*/
public String getPrincipal() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
/**
* Get the principals of the logged in user, in this case the distinguished name.
*
* @return the distinguished name of the logged in user.
*/
public String getPrincipal() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication == null) {
log.warn("No Authentication object set in SecurityContext - returning empty String as Principal");
return "";
}
if (authentication == null) {
log.warn("No Authentication object set in SecurityContext - returning empty String as Principal");
return "";
}
Object principal = authentication.getPrincipal();
Object principal = authentication.getPrincipal();
if (principal instanceof LdapUserDetails) {
LdapUserDetails details = (LdapUserDetails) principal;
return details.getDn();
} else if (authentication instanceof AnonymousAuthenticationToken) {
if (log.isDebugEnabled()) {
log.debug("Anonymous Authentication, returning empty String as Principal");
}
return "";
} else {
throw new IllegalArgumentException("The principal property of the authentication object"
+ "needs to be an LdapUserDetails.");
}
}
if (principal instanceof LdapUserDetails) {
LdapUserDetails details = (LdapUserDetails) principal;
return details.getDn();
}
else if (authentication instanceof AnonymousAuthenticationToken) {
if (log.isDebugEnabled()) {
log.debug("Anonymous Authentication, returning empty String as Principal");
}
return "";
}
else {
throw new IllegalArgumentException(
"The principal property of the authentication object"
+ "needs to be an LdapUserDetails.");
}
}
/**
* @see org.springframework.ldap.core.AuthenticationSource#getCredentials()
*/
public String getCredentials() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
/**
* @see org.springframework.ldap.core.AuthenticationSource#getCredentials()
*/
public String getCredentials() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication == null) {
log.warn("No Authentication object set in SecurityContext - returning empty String as Credentials");
return "";
}
if (authentication == null) {
log.warn("No Authentication object set in SecurityContext - returning empty String as Credentials");
return "";
}
return (String) authentication.getCredentials();
}
return (String) authentication.getCredentials();
}
}
@@ -9,22 +9,24 @@ import org.springframework.ldap.core.DirContextOperations;
import org.springframework.util.Assert;
/**
* Simple LdapAuthoritiesPopulator which delegates to a UserDetailsService, using the name which
* was supplied at login as the username.
* Simple LdapAuthoritiesPopulator which delegates to a UserDetailsService, using the name
* which was supplied at login as the username.
*
*
* @author Luke Taylor
* @since 2.0
*/
public class UserDetailsServiceLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
private final UserDetailsService userDetailsService;
public class UserDetailsServiceLdapAuthoritiesPopulator implements
LdapAuthoritiesPopulator {
private final UserDetailsService userDetailsService;
public UserDetailsServiceLdapAuthoritiesPopulator(UserDetailsService userService) {
Assert.notNull(userService, "userDetailsService cannot be null");
this.userDetailsService = userService;
}
public UserDetailsServiceLdapAuthoritiesPopulator(UserDetailsService userService) {
Assert.notNull(userService, "userDetailsService cannot be null");
this.userDetailsService = userService;
}
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
return userDetailsService.loadUserByUsername(username).getAuthorities();
}
public Collection<? extends GrantedAuthority> getGrantedAuthorities(
DirContextOperations userData, String username) {
return userDetailsService.loadUserByUsername(username).getAuthorities();
}
}
@@ -16,15 +16,18 @@ import org.springframework.security.core.AuthenticationException;
/**
* <p>
* Thrown as a translation of an {@link javax.naming.AuthenticationException} when attempting to authenticate against
* Active Directory using {@link ActiveDirectoryLdapAuthenticationProvider}. Typically this error is wrapped by an
* {@link AuthenticationException} since it does not provide a user friendly message. When wrapped, the original
* Exception can be caught and {@link ActiveDirectoryAuthenticationException} can be accessed using
* Thrown as a translation of an {@link javax.naming.AuthenticationException} when
* attempting to authenticate against Active Directory using
* {@link ActiveDirectoryLdapAuthenticationProvider}. Typically this error is wrapped by
* an {@link AuthenticationException} since it does not provide a user friendly message.
* When wrapped, the original Exception can be caught and
* {@link ActiveDirectoryAuthenticationException} can be accessed using
* {@link AuthenticationException#getCause()} for custom error handling.
* </p>
* <p>
* The {@link #getDataCode()} will return the error code associated with the data portion of the error message. For
* example, the following error message would return 773 for {@link #getDataCode()}.
* The {@link #getDataCode()} will return the error code associated with the data portion
* of the error message. For example, the following error message would return 773 for
* {@link #getDataCode()}.
* </p>
*
* <pre>
@@ -35,14 +38,15 @@ import org.springframework.security.core.AuthenticationException;
*/
@SuppressWarnings("serial")
public final class ActiveDirectoryAuthenticationException extends AuthenticationException {
private final String dataCode;
private final String dataCode;
ActiveDirectoryAuthenticationException(String dataCode, String message, Throwable cause) {
super(message, cause);
this.dataCode = dataCode;
}
ActiveDirectoryAuthenticationException(String dataCode, String message,
Throwable cause) {
super(message, cause);
this.dataCode = dataCode;
}
public String getDataCode() {
return dataCode;
}
public String getDataCode() {
return dataCode;
}
}
@@ -44,23 +44,28 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Specialized LDAP authentication provider which uses Active Directory configuration conventions.
* Specialized LDAP authentication provider which uses Active Directory configuration
* conventions.
* <p>
* It will authenticate using the Active Directory
* <a href="http://msdn.microsoft.com/en-us/library/ms680857%28VS.85%29.aspx">{@code userPrincipalName}</a> or
* <a href="http://msdn.microsoft.com/en-us/library/ms679635%28v=vs.85%29.aspx">{@code sAMAccountName}</a> (or a custom
* {@link #setSearchFilter(String) searchFilter}) in the form {@code username@domain}. If the username does not
* already end with the domain name, the {@code userPrincipalName} will be built by appending the configured domain
* name to the username supplied in the authentication request. If no domain name is configured, it is assumed that
* the username will always contain the domain name.
* It will authenticate using the Active Directory <a
* href="http://msdn.microsoft.com/en-us/library/ms680857%28VS.85%29.aspx">
* {@code userPrincipalName}</a> or <a
* href="http://msdn.microsoft.com/en-us/library/ms679635%28v=vs.85%29.aspx">
* {@code sAMAccountName}</a> (or a custom {@link #setSearchFilter(String) searchFilter})
* in the form {@code username@domain}. If the username does not already end with the
* domain name, the {@code userPrincipalName} will be built by appending the configured
* domain name to the username supplied in the authentication request. If no domain name
* is configured, it is assumed that the username will always contain the domain name.
* <p>
* The user authorities are obtained from the data contained in the {@code memberOf} attribute.
* The user authorities are obtained from the data contained in the {@code memberOf}
* attribute.
*
* <h3>Active Directory Sub-Error Codes</h3>
*
* When an authentication fails, resulting in a standard LDAP 49 error code, Active Directory also supplies its own
* sub-error codes within the error message. These will be used to provide additional log information on why an
* authentication has failed. Typical examples are
* When an authentication fails, resulting in a standard LDAP 49 error code, Active
* Directory also supplies its own sub-error codes within the error message. These will be
* used to provide additional log information on why an authentication has failed. Typical
* examples are
*
* <ul>
* <li>525 - user not found</li>
@@ -73,297 +78,327 @@ import java.util.regex.Pattern;
* <li>775 - account locked</li>
* </ul>
*
* If you set the {@link #setConvertSubErrorCodesToExceptions(boolean) convertSubErrorCodesToExceptions} property to
* {@code true}, the codes will also be used to control the exception raised.
* If you set the {@link #setConvertSubErrorCodesToExceptions(boolean)
* convertSubErrorCodesToExceptions} property to {@code true}, the codes will also be used
* to control the exception raised.
*
* @author Luke Taylor
* @author Rob Winch
* @since 3.1
*/
public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
private static final Pattern SUB_ERROR_CODE = Pattern.compile(".*data\\s([0-9a-f]{3,4}).*");
public final class ActiveDirectoryLdapAuthenticationProvider extends
AbstractLdapAuthenticationProvider {
private static final Pattern SUB_ERROR_CODE = Pattern
.compile(".*data\\s([0-9a-f]{3,4}).*");
// Error codes
private static final int USERNAME_NOT_FOUND = 0x525;
private static final int INVALID_PASSWORD = 0x52e;
private static final int NOT_PERMITTED = 0x530;
private static final int PASSWORD_EXPIRED = 0x532;
private static final int ACCOUNT_DISABLED = 0x533;
private static final int ACCOUNT_EXPIRED = 0x701;
private static final int PASSWORD_NEEDS_RESET = 0x773;
private static final int ACCOUNT_LOCKED = 0x775;
// Error codes
private static final int USERNAME_NOT_FOUND = 0x525;
private static final int INVALID_PASSWORD = 0x52e;
private static final int NOT_PERMITTED = 0x530;
private static final int PASSWORD_EXPIRED = 0x532;
private static final int ACCOUNT_DISABLED = 0x533;
private static final int ACCOUNT_EXPIRED = 0x701;
private static final int PASSWORD_NEEDS_RESET = 0x773;
private static final int ACCOUNT_LOCKED = 0x775;
private final String domain;
private final String rootDn;
private final String url;
private boolean convertSubErrorCodesToExceptions;
private String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
private final String domain;
private final String rootDn;
private final String url;
private boolean convertSubErrorCodesToExceptions;
private String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
// Only used to allow tests to substitute a mock LdapContext
ContextFactory contextFactory = new ContextFactory();
// Only used to allow tests to substitute a mock LdapContext
ContextFactory contextFactory = new ContextFactory();
/**
* @param domain the domain name (may be null or empty)
* @param url an LDAP url (or multiple URLs)
* @param rootDn the root DN (may be null or empty)
*/
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url, String rootDn) {
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
this.url = url;
this.rootDn = StringUtils.hasText(rootDn) ? rootDn.toLowerCase() : null;
}
/**
* @param domain the domain name (may be null or empty)
* @param url an LDAP url (or multiple URLs)
* @param rootDn the root DN (may be null or empty)
*/
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url,
String rootDn) {
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
this.url = url;
this.rootDn = StringUtils.hasText(rootDn) ? rootDn.toLowerCase() : null;
}
/**
* @param domain the domain name (may be null or empty)
* @param url an LDAP url (or multiple URLs)
*/
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) {
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
this.url = url;
rootDn = this.domain == null ? null : rootDnFromDomain(this.domain);
}
/**
* @param domain the domain name (may be null or empty)
* @param url an LDAP url (or multiple URLs)
*/
public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) {
Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
this.url = url;
rootDn = this.domain == null ? null : rootDnFromDomain(this.domain);
}
@Override
protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth) {
String username = auth.getName();
String password = (String) auth.getCredentials();
@Override
protected DirContextOperations doAuthentication(
UsernamePasswordAuthenticationToken auth) {
String username = auth.getName();
String password = (String) auth.getCredentials();
DirContext ctx = bindAsUser(username, password);
DirContext ctx = bindAsUser(username, password);
try {
return searchForUser(ctx, username);
} catch (NamingException e) {
logger.error("Failed to locate directory entry for authenticated user: " + username, e);
throw badCredentials(e);
} finally {
LdapUtils.closeContext(ctx);
}
}
try {
return searchForUser(ctx, username);
}
catch (NamingException e) {
logger.error("Failed to locate directory entry for authenticated user: "
+ username, e);
throw badCredentials(e);
}
finally {
LdapUtils.closeContext(ctx);
}
}
/**
* Creates the user authority list from the values of the {@code memberOf} attribute obtained from the user's
* Active Directory entry.
*/
@Override
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) {
String[] groups = userData.getStringAttributes("memberOf");
/**
* Creates the user authority list from the values of the {@code memberOf} attribute
* obtained from the user's Active Directory entry.
*/
@Override
protected Collection<? extends GrantedAuthority> loadUserAuthorities(
DirContextOperations userData, String username, String password) {
String[] groups = userData.getStringAttributes("memberOf");
if (groups == null) {
logger.debug("No values for 'memberOf' attribute.");
if (groups == null) {
logger.debug("No values for 'memberOf' attribute.");
return AuthorityUtils.NO_AUTHORITIES;
}
return AuthorityUtils.NO_AUTHORITIES;
}
if (logger.isDebugEnabled()) {
logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
}
if (logger.isDebugEnabled()) {
logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
}
ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(groups.length);
ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(
groups.length);
for (String group : groups) {
authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
}
for (String group : groups) {
authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group)
.removeLast().getValue()));
}
return authorities;
}
return authorities;
}
private DirContext bindAsUser(String username, String password) {
// TODO. add DNS lookup based on domain
final String bindUrl = url;
private DirContext bindAsUser(String username, String password) {
// TODO. add DNS lookup based on domain
final String bindUrl = url;
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.SECURITY_AUTHENTICATION, "simple");
String bindPrincipal = createBindPrincipal(username);
env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
env.put(Context.PROVIDER_URL, bindUrl);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName());
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.SECURITY_AUTHENTICATION, "simple");
String bindPrincipal = createBindPrincipal(username);
env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
env.put(Context.PROVIDER_URL, bindUrl);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName());
try {
return contextFactory.createContext(env);
} catch (NamingException e) {
if ((e instanceof AuthenticationException) || (e instanceof OperationNotSupportedException)) {
handleBindException(bindPrincipal, e);
throw badCredentials(e);
} else {
throw LdapUtils.convertLdapException(e);
}
}
}
try {
return contextFactory.createContext(env);
}
catch (NamingException e) {
if ((e instanceof AuthenticationException)
|| (e instanceof OperationNotSupportedException)) {
handleBindException(bindPrincipal, e);
throw badCredentials(e);
}
else {
throw LdapUtils.convertLdapException(e);
}
}
}
private void handleBindException(String bindPrincipal, NamingException exception) {
if (logger.isDebugEnabled()) {
logger.debug("Authentication for " + bindPrincipal + " failed:" + exception);
}
private void handleBindException(String bindPrincipal, NamingException exception) {
if (logger.isDebugEnabled()) {
logger.debug("Authentication for " + bindPrincipal + " failed:" + exception);
}
int subErrorCode = parseSubErrorCode(exception.getMessage());
int subErrorCode = parseSubErrorCode(exception.getMessage());
if (subErrorCode <= 0) {
logger.debug("Failed to locate AD-specific sub-error code in message");
return;
}
if (subErrorCode <= 0) {
logger.debug("Failed to locate AD-specific sub-error code in message");
return;
}
logger.info("Active Directory authentication failed: " + subCodeToLogMessage(subErrorCode));
logger.info("Active Directory authentication failed: "
+ subCodeToLogMessage(subErrorCode));
if (convertSubErrorCodesToExceptions) {
raiseExceptionForErrorCode(subErrorCode, exception);
}
}
if (convertSubErrorCodesToExceptions) {
raiseExceptionForErrorCode(subErrorCode, exception);
}
}
private int parseSubErrorCode(String message) {
Matcher m = SUB_ERROR_CODE.matcher(message);
private int parseSubErrorCode(String message) {
Matcher m = SUB_ERROR_CODE.matcher(message);
if (m.matches()) {
return Integer.parseInt(m.group(1), 16);
}
if (m.matches()) {
return Integer.parseInt(m.group(1), 16);
}
return -1;
}
return -1;
}
private void raiseExceptionForErrorCode(int code, NamingException exception) {
String hexString = Integer.toHexString(code);
Throwable cause = new ActiveDirectoryAuthenticationException(hexString, exception.getMessage(), exception);
switch (code) {
case PASSWORD_EXPIRED:
throw new CredentialsExpiredException(messages.getMessage("LdapAuthenticationProvider.credentialsExpired",
"User credentials have expired"), cause);
case ACCOUNT_DISABLED:
throw new DisabledException(messages.getMessage("LdapAuthenticationProvider.disabled",
"User is disabled"), cause);
case ACCOUNT_EXPIRED:
throw new AccountExpiredException(messages.getMessage("LdapAuthenticationProvider.expired",
"User account has expired"), cause);
case ACCOUNT_LOCKED:
throw new LockedException(messages.getMessage("LdapAuthenticationProvider.locked",
"User account is locked"), cause);
default:
throw badCredentials(cause);
}
}
private void raiseExceptionForErrorCode(int code, NamingException exception) {
String hexString = Integer.toHexString(code);
Throwable cause = new ActiveDirectoryAuthenticationException(hexString,
exception.getMessage(), exception);
switch (code) {
case PASSWORD_EXPIRED:
throw new CredentialsExpiredException(messages.getMessage(
"LdapAuthenticationProvider.credentialsExpired",
"User credentials have expired"), cause);
case ACCOUNT_DISABLED:
throw new DisabledException(messages.getMessage(
"LdapAuthenticationProvider.disabled", "User is disabled"), cause);
case ACCOUNT_EXPIRED:
throw new AccountExpiredException(messages.getMessage(
"LdapAuthenticationProvider.expired", "User account has expired"),
cause);
case ACCOUNT_LOCKED:
throw new LockedException(messages.getMessage(
"LdapAuthenticationProvider.locked", "User account is locked"), cause);
default:
throw badCredentials(cause);
}
}
private String subCodeToLogMessage(int code) {
switch (code) {
case USERNAME_NOT_FOUND:
return "User was not found in directory";
case INVALID_PASSWORD:
return "Supplied password was invalid";
case NOT_PERMITTED:
return "User not permitted to logon at this time";
case PASSWORD_EXPIRED:
return "Password has expired";
case ACCOUNT_DISABLED:
return "Account is disabled";
case ACCOUNT_EXPIRED:
return "Account expired";
case PASSWORD_NEEDS_RESET:
return "User must reset password";
case ACCOUNT_LOCKED:
return "Account locked";
}
private String subCodeToLogMessage(int code) {
switch (code) {
case USERNAME_NOT_FOUND:
return "User was not found in directory";
case INVALID_PASSWORD:
return "Supplied password was invalid";
case NOT_PERMITTED:
return "User not permitted to logon at this time";
case PASSWORD_EXPIRED:
return "Password has expired";
case ACCOUNT_DISABLED:
return "Account is disabled";
case ACCOUNT_EXPIRED:
return "Account expired";
case PASSWORD_NEEDS_RESET:
return "User must reset password";
case ACCOUNT_LOCKED:
return "Account locked";
}
return "Unknown (error code " + Integer.toHexString(code) +")";
}
return "Unknown (error code " + Integer.toHexString(code) + ")";
}
private BadCredentialsException badCredentials() {
return new BadCredentialsException(messages.getMessage(
"LdapAuthenticationProvider.badCredentials", "Bad credentials"));
}
private BadCredentialsException badCredentials() {
return new BadCredentialsException(messages.getMessage(
"LdapAuthenticationProvider.badCredentials", "Bad credentials"));
}
private BadCredentialsException badCredentials(Throwable cause) {
return (BadCredentialsException) badCredentials().initCause(cause);
}
private BadCredentialsException badCredentials(Throwable cause) {
return (BadCredentialsException) badCredentials().initCause(cause);
}
private DirContextOperations searchForUser(DirContext context, String username) throws NamingException {
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
private DirContextOperations searchForUser(DirContext context, String username)
throws NamingException {
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String bindPrincipal = createBindPrincipal(username);
String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal);
String bindPrincipal = createBindPrincipal(username);
String searchRoot = rootDn != null ? rootDn
: searchRootFromPrincipal(bindPrincipal);
try {
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context, searchControls,
searchRoot, searchFilter, new Object[]{bindPrincipal});
} catch (IncorrectResultSizeDataAccessException incorrectResults) {
// Search should never return multiple results if properly configured - just rethrow
if (incorrectResults.getActualSize() != 0) {
throw incorrectResults;
}
// If we found no results, then the username/password did not match
UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException("User " + username
+ " not found in directory.", incorrectResults);
throw badCredentials(userNameNotFoundException);
}
}
try {
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context,
searchControls, searchRoot, searchFilter,
new Object[] { bindPrincipal });
}
catch (IncorrectResultSizeDataAccessException incorrectResults) {
// Search should never return multiple results if properly configured - just
// rethrow
if (incorrectResults.getActualSize() != 0) {
throw incorrectResults;
}
// If we found no results, then the username/password did not match
UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException(
"User " + username + " not found in directory.", incorrectResults);
throw badCredentials(userNameNotFoundException);
}
}
private String searchRootFromPrincipal(String bindPrincipal) {
int atChar = bindPrincipal.lastIndexOf('@');
private String searchRootFromPrincipal(String bindPrincipal) {
int atChar = bindPrincipal.lastIndexOf('@');
if (atChar < 0) {
logger.debug("User principal '" + bindPrincipal + "' does not contain the domain, and no domain has been configured");
throw badCredentials();
}
if (atChar < 0) {
logger.debug("User principal '" + bindPrincipal
+ "' does not contain the domain, and no domain has been configured");
throw badCredentials();
}
return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length()));
}
return rootDnFromDomain(bindPrincipal.substring(atChar + 1,
bindPrincipal.length()));
}
private String rootDnFromDomain(String domain) {
String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
StringBuilder root = new StringBuilder();
private String rootDnFromDomain(String domain) {
String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
StringBuilder root = new StringBuilder();
for (String token : tokens) {
if (root.length() > 0) {
root.append(',');
}
root.append("dc=").append(token);
}
for (String token : tokens) {
if (root.length() > 0) {
root.append(',');
}
root.append("dc=").append(token);
}
return root.toString();
}
return root.toString();
}
String createBindPrincipal(String username) {
if (domain == null || username.toLowerCase().endsWith(domain)) {
return username;
}
String createBindPrincipal(String username) {
if (domain == null || username.toLowerCase().endsWith(domain)) {
return username;
}
return username + "@" + domain;
}
return username + "@" + domain;
}
/**
* By default, a failed authentication (LDAP error 49) will result in a {@code BadCredentialsException}.
* <p>
* If this property is set to {@code true}, the exception message from a failed bind attempt will be parsed
* for the AD-specific error code and a {@link CredentialsExpiredException}, {@link DisabledException},
* {@link AccountExpiredException} or {@link LockedException} will be thrown for the corresponding codes. All
* other codes will result in the default {@code BadCredentialsException}.
*
* @param convertSubErrorCodesToExceptions {@code true} to raise an exception based on the AD error code.
*/
public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToExceptions) {
this.convertSubErrorCodesToExceptions = convertSubErrorCodesToExceptions;
}
/**
* By default, a failed authentication (LDAP error 49) will result in a
* {@code BadCredentialsException}.
* <p>
* If this property is set to {@code true}, the exception message from a failed bind
* attempt will be parsed for the AD-specific error code and a
* {@link CredentialsExpiredException}, {@link DisabledException},
* {@link AccountExpiredException} or {@link LockedException} will be thrown for the
* corresponding codes. All other codes will result in the default
* {@code BadCredentialsException}.
*
* @param convertSubErrorCodesToExceptions {@code true} to raise an exception based on
* the AD error code.
*/
public void setConvertSubErrorCodesToExceptions(
boolean convertSubErrorCodesToExceptions) {
this.convertSubErrorCodesToExceptions = convertSubErrorCodesToExceptions;
}
/**
* The LDAP filter string to search for the user being authenticated.
* Occurrences of {0} are replaced with the {@code username@domain}.
* <p>
* Defaults to: {@code (&(objectClass=user)(userPrincipalName={0}))}
* </p>
*
* @param searchFilter the filter string
*
* @since 3.2.6
*/
public void setSearchFilter(String searchFilter) {
Assert.hasText(searchFilter,"searchFilter must have text");
this.searchFilter = searchFilter;
}
/**
* The LDAP filter string to search for the user being authenticated. Occurrences of
* {0} are replaced with the {@code username@domain}.
* <p>
* Defaults to: {@code (&(objectClass=user)(userPrincipalName= 0}))}
* </p>
*
* @param searchFilter the filter string
*
* @since 3.2.6
*/
public void setSearchFilter(String searchFilter) {
Assert.hasText(searchFilter, "searchFilter must have text");
this.searchFilter = searchFilter;
}
static class ContextFactory {
DirContext createContext(Hashtable<?,?> env) throws NamingException {
return new InitialLdapContext(env, null);
}
}
static class ContextFactory {
DirContext createContext(Hashtable<?, ?> env) throws NamingException {
return new InitialLdapContext(env, null);
}
}
}
@@ -10,77 +10,83 @@ import javax.naming.ldap.LdapContext;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
/**
* Extended version of the <tt>DefaultSpringSecurityContextSource</tt> which adds support for
* the use of {@link PasswordPolicyControl} to make use of user account data stored in the directory.
* Extended version of the <tt>DefaultSpringSecurityContextSource</tt> which adds support
* for the use of {@link PasswordPolicyControl} to make use of user account data stored in
* the directory.
* <p>
* When binding with specific username (not the <tt>userDn</tt>) property it will connect
* first as the userDn, then reconnect as the user in order to retrieve any password-policy control
* sent with the response, even if an exception occurs.
* first as the userDn, then reconnect as the user in order to retrieve any
* password-policy control sent with the response, even if an exception occurs.
*
* @author Luke Taylor
* @since 3.0
*/
public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityContextSource {
public PasswordPolicyAwareContextSource(String providerUrl) {
super(providerUrl);
}
public PasswordPolicyAwareContextSource(String providerUrl) {
super(providerUrl);
}
@Override
public DirContext getContext(String principal, String credentials) throws PasswordPolicyException {
if (principal.equals(userDn)) {
return super.getContext(principal, credentials);
}
@Override
public DirContext getContext(String principal, String credentials)
throws PasswordPolicyException {
if (principal.equals(userDn)) {
return super.getContext(principal, credentials);
}
final boolean debug = logger.isDebugEnabled();
final boolean debug = logger.isDebugEnabled();
if (debug) {
logger.debug("Binding as '" + userDn + "', prior to reconnect as user '" + principal + "'" );
}
if (debug) {
logger.debug("Binding as '" + userDn + "', prior to reconnect as user '"
+ principal + "'");
}
// First bind as manager user before rebinding as the specific principal.
LdapContext ctx = (LdapContext) super.getContext(userDn, password);
// First bind as manager user before rebinding as the specific principal.
LdapContext ctx = (LdapContext) super.getContext(userDn, password);
Control[] rctls = { new PasswordPolicyControl(false) };
Control[] rctls = { new PasswordPolicyControl(false) };
try {
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal );
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
ctx.reconnect(rctls);
} catch(javax.naming.NamingException ne) {
PasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor.extractControl(ctx);
if (debug) {
logger.debug("Failed to obtain context", ne);
logger.debug("Password policy response: " + ctrl);
}
try {
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal);
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
ctx.reconnect(rctls);
}
catch (javax.naming.NamingException ne) {
PasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor
.extractControl(ctx);
if (debug) {
logger.debug("Failed to obtain context", ne);
logger.debug("Password policy response: " + ctrl);
}
LdapUtils.closeContext(ctx);
LdapUtils.closeContext(ctx);
if (ctrl != null) {
if (ctrl.isLocked()) {
throw new PasswordPolicyException(ctrl.getErrorStatus());
}
}
if (ctrl != null) {
if (ctrl.isLocked()) {
throw new PasswordPolicyException(ctrl.getErrorStatus());
}
}
throw LdapUtils.convertLdapException(ne);
}
throw LdapUtils.convertLdapException(ne);
}
if (debug) {
logger.debug("PPolicy control returned: " + PasswordPolicyControlExtractor.extractControl(ctx));
}
if (debug) {
logger.debug("PPolicy control returned: "
+ PasswordPolicyControlExtractor.extractControl(ctx));
}
return ctx;
}
return ctx;
}
@Override
@SuppressWarnings("unchecked")
protected Hashtable getAuthenticatedEnv(String principal, String credentials) {
Hashtable env = super.getAuthenticatedEnv(principal, credentials);
@Override
@SuppressWarnings("unchecked")
protected Hashtable getAuthenticatedEnv(String principal, String credentials) {
Hashtable env = super.getAuthenticatedEnv(principal, credentials);
env.put(LdapContext.CONTROL_FACTORIES, PasswordPolicyControlFactory.class.getName());
env.put(LdapContext.CONTROL_FACTORIES,
PasswordPolicyControlFactory.class.getName());
return env;
}
return env;
}
}
@@ -17,14 +17,13 @@ package org.springframework.security.ldap.ppolicy;
import javax.naming.ldap.Control;
/**
*
* A Password Policy request control.
* <p>
* Based on the information in the corresponding
* <a href="http://tools.ietf.org/draft/draft-behera-ldap-password-policy/draft-behera-ldap-password-policy-09.txt">
* internet draft on LDAP password policy</a>
* Based on the information in the corresponding <a href=
* "http://tools.ietf.org/draft/draft-behera-ldap-password-policy/draft-behera-ldap-password-policy-09.txt"
* > internet draft on LDAP password policy</a>
*
* @author Stefan Zoerner
* @author Luke Taylor
@@ -32,56 +31,60 @@ import javax.naming.ldap.Control;
* @see PasswordPolicyResponseControl
*/
public class PasswordPolicyControl implements Control {
//~ Static fields/initializers =====================================================================================
// ~ Static fields/initializers
// =====================================================================================
/** OID of the Password Policy Control */
public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1";
/** OID of the Password Policy Control */
public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1";
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private final boolean critical;
private final boolean critical;
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
/**
* Creates a non-critical (request) control.
*/
public PasswordPolicyControl() {
this(Control.NONCRITICAL);
}
/**
* Creates a non-critical (request) control.
*/
public PasswordPolicyControl() {
this(Control.NONCRITICAL);
}
/**
* Creates a (request) control.
*
* @param critical indicates whether the control is critical for the client
*/
public PasswordPolicyControl(boolean critical) {
this.critical = critical;
}
/**
* Creates a (request) control.
*
* @param critical indicates whether the control is critical for the client
*/
public PasswordPolicyControl(boolean critical) {
this.critical = critical;
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* Retrieves the ASN.1 BER encoded value of the LDAP control. The request value for this control is always
* empty.
*
* @return always null
*/
public byte[] getEncodedValue() {
return null;
}
/**
* Retrieves the ASN.1 BER encoded value of the LDAP control. The request value for
* this control is always empty.
*
* @return always null
*/
public byte[] getEncodedValue() {
return null;
}
/**
* Returns the OID of the Password Policy Control ("1.3.6.1.4.1.42.2.27.8.5.1").
*/
public String getID() {
return OID;
}
/**
* Returns the OID of the Password Policy Control ("1.3.6.1.4.1.42.2.27.8.5.1").
*/
public String getID() {
return OID;
}
/**
* Returns whether the control is critical for the client.
*/
public boolean isCritical() {
return critical;
}
/**
* Returns whether the control is critical for the client.
*/
public boolean isCritical() {
return critical;
}
}
@@ -14,24 +14,26 @@ import org.apache.commons.logging.LogFactory;
* @since 3.0
*/
public class PasswordPolicyControlExtractor {
private static final Log logger = LogFactory.getLog(PasswordPolicyControlExtractor.class);
private static final Log logger = LogFactory
.getLog(PasswordPolicyControlExtractor.class);
public static PasswordPolicyResponseControl extractControl(DirContext dirCtx) {
LdapContext ctx = (LdapContext) dirCtx;
Control[] ctrls = null;
try {
ctrls = ctx.getResponseControls();
} catch (javax.naming.NamingException e) {
logger.error("Failed to obtain response controls", e);
}
public static PasswordPolicyResponseControl extractControl(DirContext dirCtx) {
LdapContext ctx = (LdapContext) dirCtx;
Control[] ctrls = null;
try {
ctrls = ctx.getResponseControls();
}
catch (javax.naming.NamingException e) {
logger.error("Failed to obtain response controls", e);
}
for (int i = 0; ctrls != null && i < ctrls.length; i++) {
if (ctrls[i] instanceof PasswordPolicyResponseControl) {
return (PasswordPolicyResponseControl) ctrls[i];
}
}
for (int i = 0; ctrls != null && i < ctrls.length; i++) {
if (ctrls[i] instanceof PasswordPolicyResponseControl) {
return (PasswordPolicyResponseControl) ctrls[i];
}
}
return null;
}
return null;
}
}
@@ -18,7 +18,6 @@ package org.springframework.security.ldap.ppolicy;
import javax.naming.ldap.Control;
import javax.naming.ldap.ControlFactory;
/**
* Transforms a control object to a PasswordPolicyResponseControl object, if appropriate.
*
@@ -26,21 +25,23 @@ import javax.naming.ldap.ControlFactory;
* @author Luke Taylor
*/
public class PasswordPolicyControlFactory extends ControlFactory {
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* Creates an instance of PasswordPolicyResponseControl if the passed control is a response control of this
* type. Attributes of the result are filled with the correct values (e.g. error code).
*
* @param ctl the control the check
*
* @return a response control of type PasswordPolicyResponseControl, or null
*/
public Control getControlInstance(Control ctl) {
if (ctl.getID().equals(PasswordPolicyControl.OID)) {
return new PasswordPolicyResponseControl(ctl.getEncodedValue());
}
/**
* Creates an instance of PasswordPolicyResponseControl if the passed control is a
* response control of this type. Attributes of the result are filled with the correct
* values (e.g. error code).
*
* @param ctl the control the check
*
* @return a response control of type PasswordPolicyResponseControl, or null
*/
public Control getControlInstance(Control ctl) {
if (ctl.getID().equals(PasswordPolicyControl.OID)) {
return new PasswordPolicyResponseControl(ctl.getEncodedValue());
}
return null;
}
return null;
}
}
@@ -5,7 +5,7 @@ package org.springframework.security.ldap.ppolicy;
* @since 3.0
*/
public interface PasswordPolicyData {
int getTimeBeforeExpiration();
int getTimeBeforeExpiration();
int getGraceLoginsRemaining();
int getGraceLoginsRemaining();
}
@@ -1,9 +1,8 @@
package org.springframework.security.ldap.ppolicy;
/**
* Defines status codes for use with <tt>PasswordPolicyException</tt>, with error codes (for message source lookup) and default
* messages.
* Defines status codes for use with <tt>PasswordPolicyException</tt>, with error codes
* (for message source lookup) and default messages.
*
* <pre>
* PasswordPolicyResponseValue ::= SEQUENCE {
@@ -19,35 +18,38 @@ package org.springframework.security.ldap.ppolicy;
* passwordInHistory (8)
* } OPTIONAL
* }
*</pre>
* </pre>
*
* @author Luke Taylor
* @since 3.0
*/
public enum PasswordPolicyErrorStatus {
PASSWORD_EXPIRED ("ppolicy.expired", "Your password has expired"),
ACCOUNT_LOCKED ("ppolicy.locked", "Account is locked"),
CHANGE_AFTER_RESET ("ppolicy.change.after.reset", "Your password must be changed after being reset"),
PASSWORD_MOD_NOT_ALLOWED ("ppolicy.mod.not.allowed", "Password cannot be changed"),
MUST_SUPPLY_OLD_PASSWORD ("ppolicy.must.supply.old.password", "The old password must be supplied"),
INSUFFICIENT_PASSWORD_QUALITY ("ppolicy.insufficient.password.quality", "The supplied password is of insufficient quality"),
PASSWORD_TOO_SHORT ("ppolicy.password.too.short", "The supplied password is too short"),
PASSWORD_TOO_YOUNG ("ppolicy.password.too.young", "Your password was changed too recently to be changed again"),
PASSWORD_IN_HISTORY ("ppolicy.password.in.history", "The supplied password has already been used");
PASSWORD_EXPIRED("ppolicy.expired", "Your password has expired"), ACCOUNT_LOCKED(
"ppolicy.locked", "Account is locked"), CHANGE_AFTER_RESET(
"ppolicy.change.after.reset",
"Your password must be changed after being reset"), PASSWORD_MOD_NOT_ALLOWED(
"ppolicy.mod.not.allowed", "Password cannot be changed"), MUST_SUPPLY_OLD_PASSWORD(
"ppolicy.must.supply.old.password", "The old password must be supplied"), INSUFFICIENT_PASSWORD_QUALITY(
"ppolicy.insufficient.password.quality",
"The supplied password is of insufficient quality"), PASSWORD_TOO_SHORT(
"ppolicy.password.too.short", "The supplied password is too short"), PASSWORD_TOO_YOUNG(
"ppolicy.password.too.young",
"Your password was changed too recently to be changed again"), PASSWORD_IN_HISTORY(
"ppolicy.password.in.history", "The supplied password has already been used");
private final String errorCode;
private final String defaultMessage;
private final String errorCode;
private final String defaultMessage;
private PasswordPolicyErrorStatus(String errorCode, String defaultMessage) {
this.errorCode = errorCode;
this.defaultMessage = defaultMessage;
}
private PasswordPolicyErrorStatus(String errorCode, String defaultMessage) {
this.errorCode = errorCode;
this.defaultMessage = defaultMessage;
}
public String getErrorCode() {
return errorCode;
}
public String getErrorCode() {
return errorCode;
}
public String getDefaultMessage() {
return defaultMessage;
}
public String getDefaultMessage() {
return defaultMessage;
}
}
@@ -3,20 +3,21 @@ package org.springframework.security.ldap.ppolicy;
/**
* Generic exception raised by the ppolicy package.
* <p>
* The <tt>status</tt> property should be checked for more detail on the cause of the exception.
* The <tt>status</tt> property should be checked for more detail on the cause of the
* exception.
*
* @author Luke Taylor
* @since 3.0
*/
public class PasswordPolicyException extends RuntimeException {
private final PasswordPolicyErrorStatus status;
private final PasswordPolicyErrorStatus status;
public PasswordPolicyException(PasswordPolicyErrorStatus status) {
super(status.getDefaultMessage());
this.status = status;
}
public PasswordPolicyException(PasswordPolicyErrorStatus status) {
super(status.getDefaultMessage());
this.status = status;
}
public PasswordPolicyErrorStatus getStatus() {
return status;
}
public PasswordPolicyErrorStatus getStatus() {
return status;
}
}
@@ -32,11 +32,11 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataRetrievalFailureException;
/**
* Represents the response control received when a <tt>PasswordPolicyControl</tt> is used when binding to a
* directory. Currently tested with the OpenLDAP 2.3.19 implementation of the LDAP Password Policy Draft. It extends
* the request control with the control specific data. This is accomplished by the properties <tt>timeBeforeExpiration</tt>,
* Represents the response control received when a <tt>PasswordPolicyControl</tt> is used
* when binding to a directory. Currently tested with the OpenLDAP 2.3.19 implementation
* of the LDAP Password Policy Draft. It extends the request control with the control
* specific data. This is accomplished by the properties <tt>timeBeforeExpiration</tt>,
* <tt>graceLoginsRemaining</tt>.
* <p>
*
@@ -45,304 +45,329 @@ import org.springframework.dao.DataRetrievalFailureException;
* @author Luke Taylor
*
* @see org.springframework.security.ldap.ppolicy.PasswordPolicyControl
* @see <a href="http://www.ibm.com/developerworks/tivoli/library/t-ldap-controls/">Stefan Zoerner's IBM developerworks
* article on LDAP controls.</a>
* @see <a href="http://www.ibm.com/developerworks/tivoli/library/t-ldap-controls/">Stefan
* Zoerner's IBM developerworks article on LDAP controls.</a>
*/
public class PasswordPolicyResponseControl extends PasswordPolicyControl {
//~ Static fields/initializers =====================================================================================
// ~ Static fields/initializers
// =====================================================================================
private static final Log logger = LogFactory.getLog(PasswordPolicyResponseControl.class);
private static final Log logger = LogFactory
.getLog(PasswordPolicyResponseControl.class);
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private final byte[] encodedValue;
private final byte[] encodedValue;
private PasswordPolicyErrorStatus errorStatus;
private PasswordPolicyErrorStatus errorStatus;
private int graceLoginsRemaining = Integer.MAX_VALUE;
private int timeBeforeExpiration = Integer.MAX_VALUE;
private int graceLoginsRemaining = Integer.MAX_VALUE;
private int timeBeforeExpiration = Integer.MAX_VALUE;
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
/**
* Decodes the Ber encoded control data. The ASN.1 value of the control data is:<pre>
* PasswordPolicyResponseValue ::= SEQUENCE { warning [0] CHOICE {
* timeBeforeExpiration [0] INTEGER (0 .. maxInt),
* graceAuthNsRemaining [1] INTEGER (0 .. maxInt) } OPTIONAL, error [1] ENUMERATED {
* passwordExpired (0), accountLocked (1),
* changeAfterReset (2), passwordModNotAllowed (3),
* mustSupplyOldPassword (4), insufficientPasswordQuality (5),
* passwordTooShort (6), passwordTooYoung (7),
* passwordInHistory (8) } OPTIONAL }</pre>
*
*/
public PasswordPolicyResponseControl(byte[] encodedValue) {
this.encodedValue = encodedValue;
/**
* Decodes the Ber encoded control data. The ASN.1 value of the control data is:
*
* <pre>
* PasswordPolicyResponseValue ::= SEQUENCE { warning [0] CHOICE {
* timeBeforeExpiration [0] INTEGER (0 .. maxInt),
* graceAuthNsRemaining [1] INTEGER (0 .. maxInt) } OPTIONAL, error [1] ENUMERATED {
* passwordExpired (0), accountLocked (1),
* changeAfterReset (2), passwordModNotAllowed (3),
* mustSupplyOldPassword (4), insufficientPasswordQuality (5),
* passwordTooShort (6), passwordTooYoung (7),
* passwordInHistory (8) } OPTIONAL }
* </pre>
*
*/
public PasswordPolicyResponseControl(byte[] encodedValue) {
this.encodedValue = encodedValue;
//PPolicyDecoder decoder = new JLdapDecoder();
PPolicyDecoder decoder = new NetscapeDecoder();
// PPolicyDecoder decoder = new JLdapDecoder();
PPolicyDecoder decoder = new NetscapeDecoder();
try {
decoder.decode();
} catch (IOException e) {
throw new DataRetrievalFailureException("Failed to parse control value", e);
}
}
try {
decoder.decode();
}
catch (IOException e) {
throw new DataRetrievalFailureException("Failed to parse control value", e);
}
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* Returns the unchanged value of the response control. Returns the unchanged value of the response
* control as byte array.
*/
public byte[] getEncodedValue() {
return encodedValue;
}
/**
* Returns the unchanged value of the response control. Returns the unchanged value of
* the response control as byte array.
*/
public byte[] getEncodedValue() {
return encodedValue;
}
public PasswordPolicyErrorStatus getErrorStatus() {
return errorStatus;
}
public PasswordPolicyErrorStatus getErrorStatus() {
return errorStatus;
}
/**
* Returns the graceLoginsRemaining.
*
* @return Returns the graceLoginsRemaining.
*/
public int getGraceLoginsRemaining() {
return graceLoginsRemaining;
}
/**
* Returns the graceLoginsRemaining.
*
* @return Returns the graceLoginsRemaining.
*/
public int getGraceLoginsRemaining() {
return graceLoginsRemaining;
}
/**
* Returns the timeBeforeExpiration.
*
* @return Returns the time before expiration in seconds
*/
public int getTimeBeforeExpiration() {
return timeBeforeExpiration;
}
/**
* Returns the timeBeforeExpiration.
*
* @return Returns the time before expiration in seconds
*/
public int getTimeBeforeExpiration() {
return timeBeforeExpiration;
}
/**
* Checks whether an error is present.
*
* @return true, if an error is present
*/
public boolean hasError() {
return errorStatus != null;
}
/**
* Checks whether an error is present.
*
* @return true, if an error is present
*/
public boolean hasError() {
return errorStatus != null;
}
/**
* Checks whether a warning is present.
*
* @return true, if a warning is present
*/
public boolean hasWarning() {
return (graceLoginsRemaining != Integer.MAX_VALUE) || (timeBeforeExpiration != Integer.MAX_VALUE);
}
/**
* Checks whether a warning is present.
*
* @return true, if a warning is present
*/
public boolean hasWarning() {
return (graceLoginsRemaining != Integer.MAX_VALUE)
|| (timeBeforeExpiration != Integer.MAX_VALUE);
}
public boolean isExpired() {
return errorStatus == PasswordPolicyErrorStatus.PASSWORD_EXPIRED;
}
public boolean isExpired() {
return errorStatus == PasswordPolicyErrorStatus.PASSWORD_EXPIRED;
}
public boolean isChangeAfterReset() {
return errorStatus == PasswordPolicyErrorStatus.CHANGE_AFTER_RESET;
}
public boolean isChangeAfterReset() {
return errorStatus == PasswordPolicyErrorStatus.CHANGE_AFTER_RESET;
}
public boolean isUsingGraceLogins() {
return graceLoginsRemaining < Integer.MAX_VALUE;
}
public boolean isUsingGraceLogins() {
return graceLoginsRemaining < Integer.MAX_VALUE;
}
/**
* Determines whether an account locked error has been returned.
*
* @return true if the account is locked.
*/
public boolean isLocked() {
return errorStatus == PasswordPolicyErrorStatus.ACCOUNT_LOCKED;
}
/**
* Determines whether an account locked error has been returned.
*
* @return true if the account is locked.
*/
public boolean isLocked() {
return errorStatus == PasswordPolicyErrorStatus.ACCOUNT_LOCKED;
}
/**
* Create a textual representation containing error and warning messages, if any are present.
*
* @return error and warning messages
*/
public String toString() {
StringBuilder sb = new StringBuilder("PasswordPolicyResponseControl");
/**
* Create a textual representation containing error and warning messages, if any are
* present.
*
* @return error and warning messages
*/
public String toString() {
StringBuilder sb = new StringBuilder("PasswordPolicyResponseControl");
if (hasError()) {
sb.append(", error: ").append(errorStatus.getDefaultMessage());
}
if (hasError()) {
sb.append(", error: ").append(errorStatus.getDefaultMessage());
}
if (graceLoginsRemaining != Integer.MAX_VALUE) {
sb.append(", warning: ").append(graceLoginsRemaining).append(" grace logins remain");
}
if (graceLoginsRemaining != Integer.MAX_VALUE) {
sb.append(", warning: ").append(graceLoginsRemaining)
.append(" grace logins remain");
}
if (timeBeforeExpiration != Integer.MAX_VALUE) {
sb.append(", warning: time before expiration is ").append(timeBeforeExpiration);
}
if (timeBeforeExpiration != Integer.MAX_VALUE) {
sb.append(", warning: time before expiration is ").append(
timeBeforeExpiration);
}
if (!hasError() && !hasWarning()) {
sb.append(" (no error, no warning)");
}
if (!hasError() && !hasWarning()) {
sb.append(" (no error, no warning)");
}
return sb.toString();
}
return sb.toString();
}
//~ Inner Interfaces ===============================================================================================
// ~ Inner Interfaces
// ===============================================================================================
private interface PPolicyDecoder {
void decode() throws IOException;
}
private interface PPolicyDecoder {
void decode() throws IOException;
}
//~ Inner Classes ==================================================================================================
// ~ Inner Classes
// ==================================================================================================
/**
* Decoder based on Netscape ldapsdk library
*/
private class NetscapeDecoder implements PPolicyDecoder {
public void decode() throws IOException {
int[] bread = {0};
BERSequence seq = (BERSequence) BERElement.getElement(new SpecificTagDecoder(),
new ByteArrayInputStream(encodedValue), bread);
/**
* Decoder based on Netscape ldapsdk library
*/
private class NetscapeDecoder implements PPolicyDecoder {
public void decode() throws IOException {
int[] bread = { 0 };
BERSequence seq = (BERSequence) BERElement.getElement(
new SpecificTagDecoder(), new ByteArrayInputStream(encodedValue),
bread);
int size = seq.size();
int size = seq.size();
if (logger.isDebugEnabled()) {
logger.debug("PasswordPolicyResponse, ASN.1 sequence has " + size + " elements");
}
if (logger.isDebugEnabled()) {
logger.debug("PasswordPolicyResponse, ASN.1 sequence has " + size
+ " elements");
}
for (int i = 0; i < seq.size(); i++) {
BERTag elt = (BERTag) seq.elementAt(i);
for (int i = 0; i < seq.size(); i++) {
BERTag elt = (BERTag) seq.elementAt(i);
int tag = elt.getTag() & 0x1F;
int tag = elt.getTag() & 0x1F;
if (tag == 0) {
BERChoice warning = (BERChoice) elt.getValue();
if (tag == 0) {
BERChoice warning = (BERChoice) elt.getValue();
BERTag content = (BERTag) warning.getValue();
int value = ((BERInteger) content.getValue()).getValue();
BERTag content = (BERTag) warning.getValue();
int value = ((BERInteger) content.getValue()).getValue();
if ((content.getTag() & 0x1F) == 0) {
timeBeforeExpiration = value;
} else {
graceLoginsRemaining = value;
}
} else if (tag == 1) {
BERIntegral error = (BERIntegral) elt.getValue();
errorStatus = PasswordPolicyErrorStatus.values()[error.getValue()];
}
}
}
if ((content.getTag() & 0x1F) == 0) {
timeBeforeExpiration = value;
}
else {
graceLoginsRemaining = value;
}
}
else if (tag == 1) {
BERIntegral error = (BERIntegral) elt.getValue();
errorStatus = PasswordPolicyErrorStatus.values()[error.getValue()];
}
}
}
class SpecificTagDecoder extends BERTagDecoder {
/** Allows us to remember which of the two options we're decoding */
private Boolean inChoice = null;
class SpecificTagDecoder extends BERTagDecoder {
/** Allows us to remember which of the two options we're decoding */
private Boolean inChoice = null;
public BERElement getElement(BERTagDecoder decoder, int tag, InputStream stream, int[] bytesRead,
boolean[] implicit) throws IOException {
tag &= 0x1F;
implicit[0] = false;
public BERElement getElement(BERTagDecoder decoder, int tag,
InputStream stream, int[] bytesRead, boolean[] implicit)
throws IOException {
tag &= 0x1F;
implicit[0] = false;
if (tag == 0) {
// Either the choice or the time before expiry within it
if (inChoice == null) {
setInChoice(true);
if (tag == 0) {
// Either the choice or the time before expiry within it
if (inChoice == null) {
setInChoice(true);
// Read the choice length from the stream (ignored)
BERElement.readLengthOctets(stream, bytesRead);
// Read the choice length from the stream (ignored)
BERElement.readLengthOctets(stream, bytesRead);
int[] componentLength = new int[1];
BERElement choice = new BERChoice(decoder, stream, componentLength);
bytesRead[0] += componentLength[0];
int[] componentLength = new int[1];
BERElement choice = new BERChoice(decoder, stream,
componentLength);
bytesRead[0] += componentLength[0];
// inChoice = null;
return choice;
} else {
// Must be time before expiry
return new BERInteger(stream, bytesRead);
}
} else if (tag == 1) {
// Either the graceLogins or the error enumeration.
if (inChoice == null) {
// The enumeration
setInChoice(false);
// inChoice = null;
return choice;
}
else {
// Must be time before expiry
return new BERInteger(stream, bytesRead);
}
}
else if (tag == 1) {
// Either the graceLogins or the error enumeration.
if (inChoice == null) {
// The enumeration
setInChoice(false);
return new BEREnumerated(stream, bytesRead);
} else {
if (inChoice.booleanValue()) {
// graceLogins
return new BERInteger(stream, bytesRead);
}
}
}
return new BEREnumerated(stream, bytesRead);
}
else {
if (inChoice.booleanValue()) {
// graceLogins
return new BERInteger(stream, bytesRead);
}
}
}
throw new DataRetrievalFailureException("Unexpected tag " + tag);
}
throw new DataRetrievalFailureException("Unexpected tag " + tag);
}
private void setInChoice(boolean inChoice) {
this.inChoice = Boolean.valueOf(inChoice);
}
}
}
private void setInChoice(boolean inChoice) {
this.inChoice = Boolean.valueOf(inChoice);
}
}
}
/** Decoder based on the OpenLDAP/Novell JLDAP library */
/** Decoder based on the OpenLDAP/Novell JLDAP library */
// private class JLdapDecoder implements PPolicyDecoder {
//
// public void decode() throws IOException {
//
// LBERDecoder decoder = new LBERDecoder();
//
// ASN1Sequence seq = (ASN1Sequence)decoder.decode(encodedValue);
//
// if(seq == null) {
//
// }
//
// int size = seq.size();
//
// if(logger.isDebugEnabled()) {
// logger.debug("PasswordPolicyResponse, ASN.1 sequence has " +
// size + " elements");
// }
//
// for(int i=0; i < size; i++) {
//
// ASN1Tagged taggedObject = (ASN1Tagged)seq.get(i);
//
// int tag = taggedObject.getIdentifier().getTag();
//
// ASN1OctetString value = (ASN1OctetString)taggedObject.taggedValue();
// byte[] content = value.byteValue();
//
// if(tag == 0) {
// parseWarning(content, decoder);
//
// } else if(tag == 1) {
// // Error: set the code to the value
// errorCode = content[0];
// }
// }
// }
//
// private void parseWarning(byte[] content, LBERDecoder decoder) {
// // It's the warning (choice). Parse the number and set either the
// // expiry time or number of logins remaining.
// ASN1Tagged taggedObject = (ASN1Tagged)decoder.decode(content);
// int contentTag = taggedObject.getIdentifier().getTag();
// content = ((ASN1OctetString)taggedObject.taggedValue()).byteValue();
// int number;
//
// try {
// number = ((Long)decoder.decodeNumeric(new ByteArrayInputStream(content), content.length)).intValue();
// } catch(IOException e) {
// throw new LdapDataAccessException("Failed to parse number ", e);
// }
//
// if(contentTag == 0) {
// timeBeforeExpiration = number;
// } else if (contentTag == 1) {
// graceLoginsRemaining = number;
// }
// }
// }
// private class JLdapDecoder implements PPolicyDecoder {
//
// public void decode() throws IOException {
//
// LBERDecoder decoder = new LBERDecoder();
//
// ASN1Sequence seq = (ASN1Sequence)decoder.decode(encodedValue);
//
// if(seq == null) {
//
// }
//
// int size = seq.size();
//
// if(logger.isDebugEnabled()) {
// logger.debug("PasswordPolicyResponse, ASN.1 sequence has " +
// size + " elements");
// }
//
// for(int i=0; i < size; i++) {
//
// ASN1Tagged taggedObject = (ASN1Tagged)seq.get(i);
//
// int tag = taggedObject.getIdentifier().getTag();
//
// ASN1OctetString value = (ASN1OctetString)taggedObject.taggedValue();
// byte[] content = value.byteValue();
//
// if(tag == 0) {
// parseWarning(content, decoder);
//
// } else if(tag == 1) {
// // Error: set the code to the value
// errorCode = content[0];
// }
// }
// }
//
// private void parseWarning(byte[] content, LBERDecoder decoder) {
// // It's the warning (choice). Parse the number and set either the
// // expiry time or number of logins remaining.
// ASN1Tagged taggedObject = (ASN1Tagged)decoder.decode(content);
// int contentTag = taggedObject.getIdentifier().getTag();
// content = ((ASN1OctetString)taggedObject.taggedValue()).byteValue();
// int number;
//
// try {
// number = ((Long)decoder.decodeNumeric(new ByteArrayInputStream(content),
// content.length)).intValue();
// } catch(IOException e) {
// throw new LdapDataAccessException("Failed to parse number ", e);
// }
//
// if(contentTag == 0) {
// timeBeforeExpiration = number;
// } else if (contentTag == 1) {
// graceLoginsRemaining = number;
// }
// }
// }
}
@@ -18,7 +18,6 @@ package org.springframework.security.ldap.search;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -32,7 +31,6 @@ import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import javax.naming.directory.SearchControls;
/**
* LdapUserSearch implementation which uses an Ldap filter to locate the user.
*
@@ -42,139 +40,159 @@ import javax.naming.directory.SearchControls;
* @see SearchControls
*/
public class FilterBasedLdapUserSearch implements LdapUserSearch {
//~ Static fields/initializers =====================================================================================
// ~ Static fields/initializers
// =====================================================================================
private static final Log logger = LogFactory.getLog(FilterBasedLdapUserSearch.class);
private static final Log logger = LogFactory.getLog(FilterBasedLdapUserSearch.class);
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private final ContextSource contextSource;
private final ContextSource contextSource;
/**
* The LDAP SearchControls object used for the search. Shared between searches so shouldn't be modified
* once the bean has been configured.
*/
private final SearchControls searchControls = new SearchControls();
/**
* The LDAP SearchControls object used for the search. Shared between searches so
* shouldn't be modified once the bean has been configured.
*/
private final SearchControls searchControls = new SearchControls();
/** Context name to search in, relative to the base of the configured ContextSource. */
private String searchBase = "";
/** Context name to search in, relative to the base of the configured ContextSource. */
private String searchBase = "";
/**
* The filter expression used in the user search. This is an LDAP search filter (as defined in 'RFC 2254')
* with optional arguments. See the documentation for the <tt>search</tt> methods in {@link
* javax.naming.directory.DirContext DirContext} for more information.
*
* <p>In this case, the username is the only parameter.</p>
* Possible examples are:
* <ul>
* <li>(uid={0}) - this would search for a username match on the uid attribute.</li>
* </ul>
*/
private final String searchFilter;
/**
* The filter expression used in the user search. This is an LDAP search filter (as
* defined in 'RFC 2254') with optional arguments. See the documentation for the
* <tt>search</tt> methods in {@link javax.naming.directory.DirContext DirContext} for
* more information.
*
* <p>
* In this case, the username is the only parameter.
* </p>
* Possible examples are:
* <ul>
* <li>(uid={0}) - this would search for a username match on the uid attribute.</li>
* </ul>
*/
private final String searchFilter;
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
public FilterBasedLdapUserSearch(String searchBase, String searchFilter, BaseLdapPathContextSource contextSource) {
Assert.notNull(contextSource, "contextSource must not be null");
Assert.notNull(searchFilter, "searchFilter must not be null.");
Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable).");
public FilterBasedLdapUserSearch(String searchBase, String searchFilter,
BaseLdapPathContextSource contextSource) {
Assert.notNull(contextSource, "contextSource must not be null");
Assert.notNull(searchFilter, "searchFilter must not be null.");
Assert.notNull(searchBase,
"searchBase must not be null (an empty string is acceptable).");
this.searchFilter = searchFilter;
this.contextSource = contextSource;
this.searchBase = searchBase;
this.searchFilter = searchFilter;
this.contextSource = contextSource;
this.searchBase = searchBase;
setSearchSubtree(true);
setSearchSubtree(true);
if (searchBase.length() == 0) {
logger.info("SearchBase not set. Searches will be performed from the root: "
+ contextSource.getBaseLdapPath());
}
}
if (searchBase.length() == 0) {
logger.info("SearchBase not set. Searches will be performed from the root: "
+ contextSource.getBaseLdapPath());
}
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* Return the LdapUserDetails containing the user's information
*
* @param username the username to search for.
*
* @return An LdapUserDetails object containing the details of the located user's directory entry
*
* @throws UsernameNotFoundException if no matching entry is found.
*/
public DirContextOperations searchForUser(String username) {
if (logger.isDebugEnabled()) {
logger.debug("Searching for user '" + username + "', with user search " + this);
}
/**
* Return the LdapUserDetails containing the user's information
*
* @param username the username to search for.
*
* @return An LdapUserDetails object containing the details of the located user's
* directory entry
*
* @throws UsernameNotFoundException if no matching entry is found.
*/
public DirContextOperations searchForUser(String username) {
if (logger.isDebugEnabled()) {
logger.debug("Searching for user '" + username + "', with user search "
+ this);
}
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource);
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(
contextSource);
template.setSearchControls(searchControls);
template.setSearchControls(searchControls);
try {
try {
return template.searchForSingleEntry(searchBase, searchFilter, new String[] {username});
return template.searchForSingleEntry(searchBase, searchFilter,
new String[] { username });
} catch (IncorrectResultSizeDataAccessException notFound) {
if (notFound.getActualSize() == 0) {
throw new UsernameNotFoundException("User " + username + " not found in directory.");
}
// Search should never return multiple results if properly configured, so just rethrow
throw notFound;
}
}
}
catch (IncorrectResultSizeDataAccessException notFound) {
if (notFound.getActualSize() == 0) {
throw new UsernameNotFoundException("User " + username
+ " not found in directory.");
}
// Search should never return multiple results if properly configured, so just
// rethrow
throw notFound;
}
}
/**
* Sets the corresponding property on the {@link SearchControls} instance used in the search.
*
* @param deref the derefLinkFlag value as defined in SearchControls..
*/
public void setDerefLinkFlag(boolean deref) {
searchControls.setDerefLinkFlag(deref);
}
/**
* Sets the corresponding property on the {@link SearchControls} instance used in the
* search.
*
* @param deref the derefLinkFlag value as defined in SearchControls..
*/
public void setDerefLinkFlag(boolean deref) {
searchControls.setDerefLinkFlag(deref);
}
/**
* If true then searches the entire subtree as identified by context, if false (the default) then only
* searches the level identified by the context.
*
* @param searchSubtree true the underlying search controls should be set to SearchControls.SUBTREE_SCOPE
* rather than SearchControls.ONELEVEL_SCOPE.
*/
public void setSearchSubtree(boolean searchSubtree) {
searchControls.setSearchScope(searchSubtree ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE);
}
/**
* If true then searches the entire subtree as identified by context, if false (the
* default) then only searches the level identified by the context.
*
* @param searchSubtree true the underlying search controls should be set to
* SearchControls.SUBTREE_SCOPE rather than SearchControls.ONELEVEL_SCOPE.
*/
public void setSearchSubtree(boolean searchSubtree) {
searchControls.setSearchScope(searchSubtree ? SearchControls.SUBTREE_SCOPE
: SearchControls.ONELEVEL_SCOPE);
}
/**
* The time to wait before the search fails; the default is zero, meaning forever.
*
* @param searchTimeLimit the time limit for the search (in milliseconds).
*/
public void setSearchTimeLimit(int searchTimeLimit) {
searchControls.setTimeLimit(searchTimeLimit);
}
/**
* The time to wait before the search fails; the default is zero, meaning forever.
*
* @param searchTimeLimit the time limit for the search (in milliseconds).
*/
public void setSearchTimeLimit(int searchTimeLimit) {
searchControls.setTimeLimit(searchTimeLimit);
}
/**
* Specifies the attributes that will be returned as part of the search.
*<p>
* null indicates that all attributes will be returned.
* An empty array indicates no attributes are returned.
*
* @param attrs An array of attribute names identifying the attributes that
* will be returned. Can be null.
*/
public void setReturningAttributes(String[] attrs) {
searchControls.setReturningAttributes(attrs);
}
/**
* Specifies the attributes that will be returned as part of the search.
* <p>
* null indicates that all attributes will be returned. An empty array indicates no
* attributes are returned.
*
* @param attrs An array of attribute names identifying the attributes that will be
* returned. Can be null.
*/
public void setReturningAttributes(String[] attrs) {
searchControls.setReturningAttributes(attrs);
}
public String toString() {
StringBuilder sb = new StringBuilder();
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[ searchFilter: '").append(searchFilter).append("', ");
sb.append("searchBase: '").append(searchBase).append("'");
sb.append(", scope: ")
.append(searchControls.getSearchScope() == SearchControls.SUBTREE_SCOPE ? "subtree" : "single-level, ");
sb.append(", searchTimeLimit: ").append(searchControls.getTimeLimit());
sb.append(", derefLinkFlag: ").append(searchControls.getDerefLinkFlag()).append(" ]");
return sb.toString();
}
sb.append("[ searchFilter: '").append(searchFilter).append("', ");
sb.append("searchBase: '").append(searchBase).append("'");
sb.append(", scope: ")
.append(searchControls.getSearchScope() == SearchControls.SUBTREE_SCOPE ? "subtree"
: "single-level, ");
sb.append(", searchTimeLimit: ").append(searchControls.getTimeLimit());
sb.append(", derefLinkFlag: ").append(searchControls.getDerefLinkFlag())
.append(" ]");
return sb.toString();
}
}
@@ -18,27 +18,29 @@ package org.springframework.security.ldap.search;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* Obtains a user's information from the LDAP directory given a login name.
* <p>
* May be optionally used to configure the LDAP authentication implementation when
* a more sophisticated approach is required than just using a simple username->DN
* mapping.
* May be optionally used to configure the LDAP authentication implementation when a more
* sophisticated approach is required than just using a simple username->DN mapping.
* </p>
*
* @author Luke Taylor
*/
public interface LdapUserSearch {
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* Locates a single user in the directory and returns the LDAP information for that user.
*
* @param username the login name supplied to the authentication service.
*
* @return a DirContextOperations object containing the user's full DN and requested attributes.
* @throws UsernameNotFoundException if no user with the supplied name could be located by the search.
*/
DirContextOperations searchForUser(String username) throws UsernameNotFoundException;
/**
* Locates a single user in the directory and returns the LDAP information for that
* user.
*
* @param username the login name supplied to the authentication service.
*
* @return a DirContextOperations object containing the user's full DN and requested
* attributes.
* @throws UsernameNotFoundException if no user with the supplied name could be
* located by the search.
*/
DirContextOperations searchForUser(String username) throws UsernameNotFoundException;
}
@@ -49,256 +49,275 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
/**
* Provides lifecycle services for the embedded apacheDS server defined by the supplied configuration.
* Used by {code LdapServerBeanDefinitionParser}. An instance will be stored in the application context for
* each embedded server instance. It will start the server when the context is initialized and shut it down when
* it is closed. It is intended for temporary embedded use and will not retain changes across start/stop boundaries. The
* working directory is deleted on shutdown.
* Provides lifecycle services for the embedded apacheDS server defined by the supplied
* configuration. Used by {code LdapServerBeanDefinitionParser}. An instance will be
* stored in the application context for each embedded server instance. It will start the
* server when the context is initialized and shut it down when it is closed. It is
* intended for temporary embedded use and will not retain changes across start/stop
* boundaries. The working directory is deleted on shutdown.
*
* <p>
* If used repeatedly in a single JVM process with the same configuration (for example, when
* repeatedly loading an application context during testing), it's important that the
* application context is closed to allow the bean to be disposed of and the server shutdown
* prior to attempting to start it again.
* If used repeatedly in a single JVM process with the same configuration (for example,
* when repeatedly loading an application context during testing), it's important that the
* application context is closed to allow the bean to be disposed of and the server
* shutdown prior to attempting to start it again.
* <p>
* This class is intended for testing and internal security namespace use and is not considered part of
* framework public API.
* This class is intended for testing and internal security namespace use and is not
* considered part of framework public API.
*
* @author Luke Taylor
* @author Rob Winch
*/
public class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle, ApplicationContextAware {
private final Log logger = LogFactory.getLog(getClass());
public class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle,
ApplicationContextAware {
private final Log logger = LogFactory.getLog(getClass());
final DefaultDirectoryService service;
LdapServer server;
final DefaultDirectoryService service;
LdapServer server;
private ApplicationContext ctxt;
private File workingDir;
private ApplicationContext ctxt;
private File workingDir;
private boolean running;
private final String ldifResources;
private final JdbmPartition partition;
private final String root;
private int port = 53389;
private boolean running;
private final String ldifResources;
private final JdbmPartition partition;
private final String root;
private int port = 53389;
public ApacheDSContainer(String root, String ldifs) throws Exception {
this.ldifResources = ldifs;
service = new DefaultDirectoryService();
List<Interceptor> list = new ArrayList<Interceptor>();
public ApacheDSContainer(String root, String ldifs) throws Exception {
this.ldifResources = ldifs;
service = new DefaultDirectoryService();
List<Interceptor> list = new ArrayList<Interceptor>();
list.add( new NormalizationInterceptor() );
list.add( new AuthenticationInterceptor() );
list.add( new ReferralInterceptor() );
// list.add( new AciAuthorizationInterceptor() );
// list.add( new DefaultAuthorizationInterceptor() );
list.add( new ExceptionInterceptor() );
// list.add( new ChangeLogInterceptor() );
list.add( new OperationalAttributeInterceptor() );
// list.add( new SchemaInterceptor() );
list.add( new SubentryInterceptor() );
// list.add( new CollectiveAttributeInterceptor() );
// list.add( new EventInterceptor() );
// list.add( new TriggerInterceptor() );
// list.add( new JournalInterceptor() );
list.add(new NormalizationInterceptor());
list.add(new AuthenticationInterceptor());
list.add(new ReferralInterceptor());
// list.add( new AciAuthorizationInterceptor() );
// list.add( new DefaultAuthorizationInterceptor() );
list.add(new ExceptionInterceptor());
// list.add( new ChangeLogInterceptor() );
list.add(new OperationalAttributeInterceptor());
// list.add( new SchemaInterceptor() );
list.add(new SubentryInterceptor());
// list.add( new CollectiveAttributeInterceptor() );
// list.add( new EventInterceptor() );
// list.add( new TriggerInterceptor() );
// list.add( new JournalInterceptor() );
service.setInterceptors( list );
partition = new JdbmPartition();
partition.setId("rootPartition");
partition.setSuffix(root);
this.root = root;
service.addPartition(partition);
service.setExitVmOnShutdown(false);
service.setShutdownHookEnabled(false);
service.getChangeLog().setEnabled(false);
service.setDenormalizeOpAttrsEnabled(true);
}
service.setInterceptors(list);
partition = new JdbmPartition();
partition.setId("rootPartition");
partition.setSuffix(root);
this.root = root;
service.addPartition(partition);
service.setExitVmOnShutdown(false);
service.setShutdownHookEnabled(false);
service.getChangeLog().setEnabled(false);
service.setDenormalizeOpAttrsEnabled(true);
}
public void afterPropertiesSet() throws Exception {
if (workingDir == null) {
String apacheWorkDir = System.getProperty("apacheDSWorkDir");
public void afterPropertiesSet() throws Exception {
if (workingDir == null) {
String apacheWorkDir = System.getProperty("apacheDSWorkDir");
if (apacheWorkDir == null) {
apacheWorkDir = createTempDirectory("apacheds-spring-security-");
}
if (apacheWorkDir == null) {
apacheWorkDir = createTempDirectory("apacheds-spring-security-");
}
setWorkingDirectory(new File(apacheWorkDir));
}
setWorkingDirectory(new File(apacheWorkDir));
}
server = new LdapServer();
server.setDirectoryService(service);
// AbstractLdapIntegrationTests assume IPv4, so we specify the same here
server.setTransports(new TcpTransport(port));
start();
}
server = new LdapServer();
server.setDirectoryService(service);
// AbstractLdapIntegrationTests assume IPv4, so we specify the same here
server.setTransports(new TcpTransport(port));
start();
}
public void destroy() throws Exception {
stop();
}
public void destroy() throws Exception {
stop();
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctxt = applicationContext;
}
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
ctxt = applicationContext;
}
public void setWorkingDirectory(File workingDir) {
Assert.notNull(workingDir);
public void setWorkingDirectory(File workingDir) {
Assert.notNull(workingDir);
logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath());
logger.info("Setting working directory for LDAP_PROVIDER: "
+ workingDir.getAbsolutePath());
if (workingDir.exists()) {
throw new IllegalArgumentException("The specified working directory '" + workingDir.getAbsolutePath() +
"' already exists. Another directory service instance may be using it or it may be from a " +
" previous unclean shutdown. Please confirm and delete it or configure a different " +
"working directory");
}
if (workingDir.exists()) {
throw new IllegalArgumentException(
"The specified working directory '"
+ workingDir.getAbsolutePath()
+ "' already exists. Another directory service instance may be using it or it may be from a "
+ " previous unclean shutdown. Please confirm and delete it or configure a different "
+ "working directory");
}
this.workingDir = workingDir;
this.workingDir = workingDir;
service.setWorkingDirectory(workingDir);
}
service.setWorkingDirectory(workingDir);
}
public void setPort(int port) {
this.port = port;
}
public void setPort(int port) {
this.port = port;
}
public DefaultDirectoryService getService() {
return service;
}
public DefaultDirectoryService getService() {
return service;
}
public void start() {
if (isRunning()) {
return;
}
public void start() {
if (isRunning()) {
return;
}
if (service.isStarted()) {
throw new IllegalStateException("DirectoryService is already running.");
}
if (service.isStarted()) {
throw new IllegalStateException("DirectoryService is already running.");
}
logger.info("Starting directory server...");
try {
service.startup();
server.start();
} catch (Exception e) {
throw new RuntimeException("Server startup failed", e);
}
logger.info("Starting directory server...");
try {
service.startup();
server.start();
}
catch (Exception e) {
throw new RuntimeException("Server startup failed", e);
}
try {
service.getAdminSession().lookup(partition.getSuffixDn());
}
catch (LdapNameNotFoundException e) {
try {
LdapDN dn = new LdapDN(root);
Assert.isTrue(root.startsWith("dc="));
String dc = root.substring(3,root.indexOf(','));
ServerEntry entry = service.newEntry(dn);
entry.add("objectClass", "top", "domain", "extensibleObject");
entry.add("dc",dc);
service.getAdminSession().add( entry );
} catch (Exception e1) {
logger.error("Failed to create dc entry", e1);
}
} catch (Exception e) {
logger.error("Lookup failed", e);
}
try {
service.getAdminSession().lookup(partition.getSuffixDn());
}
catch (LdapNameNotFoundException e) {
try {
LdapDN dn = new LdapDN(root);
Assert.isTrue(root.startsWith("dc="));
String dc = root.substring(3, root.indexOf(','));
ServerEntry entry = service.newEntry(dn);
entry.add("objectClass", "top", "domain", "extensibleObject");
entry.add("dc", dc);
service.getAdminSession().add(entry);
}
catch (Exception e1) {
logger.error("Failed to create dc entry", e1);
}
}
catch (Exception e) {
logger.error("Lookup failed", e);
}
running = true;
running = true;
try {
importLdifs();
} catch (Exception e) {
throw new RuntimeException("Failed to import LDIF file(s)", e);
}
}
try {
importLdifs();
}
catch (Exception e) {
throw new RuntimeException("Failed to import LDIF file(s)", e);
}
}
public void stop() {
if (!isRunning()) {
return;
}
public void stop() {
if (!isRunning()) {
return;
}
logger.info("Shutting down directory server ...");
try {
server.stop();
service.shutdown();
} catch (Exception e) {
logger.error("Shutdown failed", e);
return;
}
logger.info("Shutting down directory server ...");
try {
server.stop();
service.shutdown();
}
catch (Exception e) {
logger.error("Shutdown failed", e);
return;
}
running = false;
running = false;
if (workingDir.exists()) {
logger.info("Deleting working directory " + workingDir.getAbsolutePath());
deleteDir(workingDir);
}
}
if (workingDir.exists()) {
logger.info("Deleting working directory " + workingDir.getAbsolutePath());
deleteDir(workingDir);
}
}
private void importLdifs() throws Exception {
// Import any ldif files
Resource[] ldifs;
private void importLdifs() throws Exception {
// Import any ldif files
Resource[] ldifs;
if (ctxt == null) {
// Not running within an app context
ldifs = new PathMatchingResourcePatternResolver().getResources(ldifResources);
} else {
ldifs = ctxt.getResources(ldifResources);
}
if (ctxt == null) {
// Not running within an app context
ldifs = new PathMatchingResourcePatternResolver().getResources(ldifResources);
}
else {
ldifs = ctxt.getResources(ldifResources);
}
// Note that we can't just import using the ServerContext returned
// from starting Apache DS, apparently because of the long-running issue DIRSERVER-169.
// We need a standard context.
//DirContext dirContext = contextSource.getReadWriteContext();
// Note that we can't just import using the ServerContext returned
// from starting Apache DS, apparently because of the long-running issue
// DIRSERVER-169.
// We need a standard context.
// DirContext dirContext = contextSource.getReadWriteContext();
if (ldifs == null || ldifs.length == 0) {
return;
}
if (ldifs == null || ldifs.length == 0) {
return;
}
if(ldifs.length == 1) {
String ldifFile;
if (ldifs.length == 1) {
String ldifFile;
try {
ldifFile = ldifs[0].getFile().getAbsolutePath();
} catch (IOException e) {
ldifFile = ldifs[0].getURI().toString();
}
logger.info("Loading LDIF file: " + ldifFile);
LdifFileLoader loader = new LdifFileLoader(service.getAdminSession(), new File(ldifFile), null, getClass().getClassLoader());
loader.execute();
} else {
throw new IllegalArgumentException("More than one LDIF resource found with the supplied pattern:" + ldifResources+ " Got " + Arrays.toString(ldifs));
}
}
try {
ldifFile = ldifs[0].getFile().getAbsolutePath();
}
catch (IOException e) {
ldifFile = ldifs[0].getURI().toString();
}
logger.info("Loading LDIF file: " + ldifFile);
LdifFileLoader loader = new LdifFileLoader(service.getAdminSession(),
new File(ldifFile), null, getClass().getClassLoader());
loader.execute();
}
else {
throw new IllegalArgumentException(
"More than one LDIF resource found with the supplied pattern:"
+ ldifResources + " Got " + Arrays.toString(ldifs));
}
}
private String createTempDirectory(String prefix) throws IOException {
String parentTempDir = System.getProperty("java.io.tmpdir");
String fileNamePrefix = prefix + System.nanoTime();
String fileName = fileNamePrefix;
private String createTempDirectory(String prefix) throws IOException {
String parentTempDir = System.getProperty("java.io.tmpdir");
String fileNamePrefix = prefix + System.nanoTime();
String fileName = fileNamePrefix;
for(int i=0;i<1000;i++) {
File tempDir = new File(parentTempDir, fileName);
if(!tempDir.exists()) {
return tempDir.getAbsolutePath();
}
fileName = fileNamePrefix + "~" + i;
}
for (int i = 0; i < 1000; i++) {
File tempDir = new File(parentTempDir, fileName);
if (!tempDir.exists()) {
return tempDir.getAbsolutePath();
}
fileName = fileNamePrefix + "~" + i;
}
throw new IOException("Failed to create a temporary directory for file at " + new File(parentTempDir, fileNamePrefix));
}
throw new IOException("Failed to create a temporary directory for file at "
+ new File(parentTempDir, fileNamePrefix));
}
private boolean deleteDir(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
for (String child : children) {
boolean success = deleteDir(new File(dir, child));
if (!success) {
return false;
}
}
}
private boolean deleteDir(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
for (String child : children) {
boolean success = deleteDir(new File(dir, child));
if (!success) {
return false;
}
}
}
return dir.delete();
}
return dir.delete();
}
public boolean isRunning() {
return running;
}
public boolean isRunning() {
return running;
}
}
@@ -35,23 +35,24 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* The default strategy for obtaining user role information from the directory.
* <p>
* It obtains roles by performing a search for "groups" the user is a member of.
* <p>
* A typical group search scenario would be where each group/role is specified using the <tt>groupOfNames</tt>
* (or <tt>groupOfUniqueNames</tt>) LDAP objectClass and the user's DN is listed in the <tt>member</tt> (or
* <tt>uniqueMember</tt>) attribute to indicate that they should be assigned that role. The following LDIF sample has
* the groups stored under the DN <tt>ou=groups,dc=springframework,dc=org</tt> and a group called "developers" with
* "ben" and "luke" as members:
* A typical group search scenario would be where each group/role is specified using the
* <tt>groupOfNames</tt> (or <tt>groupOfUniqueNames</tt>) LDAP objectClass and the user's
* DN is listed in the <tt>member</tt> (or <tt>uniqueMember</tt>) attribute to indicate
* that they should be assigned that role. The following LDIF sample has the groups stored
* under the DN <tt>ou=groups,dc=springframework,dc=org</tt> and a group called
* "developers" with "ben" and "luke" as members:
*
* <pre>
* dn: ou=groups,dc=springframework,dc=org
* objectClass: top
* objectClass: organizationalUnit
* ou: groups
*
*
* dn: cn=developers,ou=groups,dc=springframework,dc=org
* objectClass: groupOfNames
* objectClass: top
@@ -62,14 +63,17 @@ import java.util.Set;
* ou: developer
* </pre>
* <p>
* The group search is performed within a DN specified by the <tt>groupSearchBase</tt> property, which should
* be relative to the root DN of its <tt>ContextSource</tt>. If the search base is null, group searching is
* disabled. The filter used in the search is defined by the <tt>groupSearchFilter</tt> property, with the filter
* argument {0} being the full DN of the user. You can also optionally use the parameter {1}, which will be substituted
* with the username. You can also specify which attribute defines the role name by setting
* the <tt>groupRoleAttribute</tt> property (the default is "cn").
* The group search is performed within a DN specified by the <tt>groupSearchBase</tt>
* property, which should be relative to the root DN of its <tt>ContextSource</tt>. If the
* search base is null, group searching is disabled. The filter used in the search is
* defined by the <tt>groupSearchFilter</tt> property, with the filter argument {0} being
* the full DN of the user. You can also optionally use the parameter {1}, which will be
* substituted with the username. You can also specify which attribute defines the role
* name by setting the <tt>groupRoleAttribute</tt> property (the default is "cn").
* <p>
* The configuration below shows how the group search might be performed with the above schema.
* The configuration below shows how the group search might be performed with the above
* schema.
*
* <pre>
* &lt;bean id="ldapAuthoritiesPopulator"
* class="org.springframework.security.authentication.ldap.populator.DefaultLdapAuthoritiesPopulator">
@@ -82,291 +86,309 @@ import java.util.Set;
* &lt;property name="convertToUpperCase" value="true"/>
* &lt;/bean>
* </pre>
* A search for roles for user "uid=ben,ou=people,dc=springframework,dc=org" would return the single granted authority
* "ROLE_DEVELOPER".
*
* A search for roles for user "uid=ben,ou=people,dc=springframework,dc=org" would return
* the single granted authority "ROLE_DEVELOPER".
* <p>
* The single-level search is performed by default. Setting the <tt>searchSubTree</tt> property to true will enable
* a search of the entire subtree under <tt>groupSearchBase</tt>.
* The single-level search is performed by default. Setting the <tt>searchSubTree</tt>
* property to true will enable a search of the entire subtree under
* <tt>groupSearchBase</tt>.
*
* @author Luke Taylor
* @author Filip Hanik
*/
public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
//~ Static fields/initializers =====================================================================================
// ~ Static fields/initializers
// =====================================================================================
private static final Log logger = LogFactory.getLog(DefaultLdapAuthoritiesPopulator.class);
private static final Log logger = LogFactory
.getLog(DefaultLdapAuthoritiesPopulator.class);
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
/**
* A default role which will be assigned to all authenticated users if set
*/
private GrantedAuthority defaultRole;
/**
* A default role which will be assigned to all authenticated users if set
*/
private GrantedAuthority defaultRole;
/**
* Template that will be used for searching
*/
private final SpringSecurityLdapTemplate ldapTemplate;
/**
* Template that will be used for searching
*/
private final SpringSecurityLdapTemplate ldapTemplate;
/**
* Controls used to determine whether group searches should be performed over the full sub-tree from the
* base DN. Modified by searchSubTree property
*/
private final SearchControls searchControls = new SearchControls();
/**
* Controls used to determine whether group searches should be performed over the full
* sub-tree from the base DN. Modified by searchSubTree property
*/
private final SearchControls searchControls = new SearchControls();
/**
* The ID of the attribute which contains the role name for a group
*/
private String groupRoleAttribute = "cn";
/**
* The ID of the attribute which contains the role name for a group
*/
private String groupRoleAttribute = "cn";
/**
* The base DN from which the search for group membership should be performed
*/
private String groupSearchBase;
/**
* The base DN from which the search for group membership should be performed
*/
private String groupSearchBase;
/**
* The pattern to be used for the user search. {0} is the user's DN
*/
private String groupSearchFilter = "(member={0})";
/**
* The role prefix that will be prepended to each role name
*/
private String rolePrefix = "ROLE_";
/**
* Should we convert the role name to uppercase
*/
private boolean convertToUpperCase = true;
/**
* The pattern to be used for the user search. {0} is the user's DN
*/
private String groupSearchFilter = "(member={0})";
/**
* The role prefix that will be prepended to each role name
*/
private String rolePrefix = "ROLE_";
/**
* Should we convert the role name to uppercase
*/
private boolean convertToUpperCase = true;
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
/**
* Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be
* set as a property.
*
* @param contextSource supplies the contexts used to search for user roles.
* @param groupSearchBase if this is an empty string the search will be performed from the root DN of the
* context factory. If null, no search will be performed.
*/
public DefaultLdapAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
Assert.notNull(contextSource, "contextSource must not be null");
ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
getLdapTemplate().setSearchControls(getSearchControls());
this.groupSearchBase = groupSearchBase;
/**
* Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be
* set as a property.
*
* @param contextSource supplies the contexts used to search for user roles.
* @param groupSearchBase if this is an empty string the search will be performed from
* the root DN of the context factory. If null, no search will be performed.
*/
public DefaultLdapAuthoritiesPopulator(ContextSource contextSource,
String groupSearchBase) {
Assert.notNull(contextSource, "contextSource must not be null");
ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
getLdapTemplate().setSearchControls(getSearchControls());
this.groupSearchBase = groupSearchBase;
if (groupSearchBase == null) {
logger.info("groupSearchBase is null. No group search will be performed.");
} else if (groupSearchBase.length() == 0) {
logger.info("groupSearchBase is empty. Searches will be performed from the context source base");
}
}
if (groupSearchBase == null) {
logger.info("groupSearchBase is null. No group search will be performed.");
}
else if (groupSearchBase.length() == 0) {
logger.info("groupSearchBase is empty. Searches will be performed from the context source base");
}
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* This method should be overridden if required to obtain any additional
* roles for the given user (on top of those obtained from the standard
* search implemented by this class).
*
* @param user the context representing the user who's roles are required
* @return the extra roles which will be merged with those returned by the group search
*/
/**
* This method should be overridden if required to obtain any additional roles for the
* given user (on top of those obtained from the standard search implemented by this
* class).
*
* @param user the context representing the user who's roles are required
* @return the extra roles which will be merged with those returned by the group
* search
*/
protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, String username) {
return null;
}
protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user,
String username) {
return null;
}
/**
* Obtains the authorities for the user who's directory entry is represented by
* the supplied LdapUserDetails object.
*
* @param user the user who's authorities are required
* @return the set of roles granted to the user.
*/
public final Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username) {
String userDn = user.getNameInNamespace();
/**
* Obtains the authorities for the user who's directory entry is represented by the
* supplied LdapUserDetails object.
*
* @param user the user who's authorities are required
* @return the set of roles granted to the user.
*/
public final Collection<GrantedAuthority> getGrantedAuthorities(
DirContextOperations user, String username) {
String userDn = user.getNameInNamespace();
if (logger.isDebugEnabled()) {
logger.debug("Getting authorities for user " + userDn);
}
if (logger.isDebugEnabled()) {
logger.debug("Getting authorities for user " + userDn);
}
Set<GrantedAuthority> roles = getGroupMembershipRoles(userDn, username);
Set<GrantedAuthority> roles = getGroupMembershipRoles(userDn, username);
Set<GrantedAuthority> extraRoles = getAdditionalRoles(user, username);
Set<GrantedAuthority> extraRoles = getAdditionalRoles(user, username);
if (extraRoles != null) {
roles.addAll(extraRoles);
}
if (extraRoles != null) {
roles.addAll(extraRoles);
}
if (defaultRole != null) {
roles.add(defaultRole);
}
if (defaultRole != null) {
roles.add(defaultRole);
}
List<GrantedAuthority> result = new ArrayList<GrantedAuthority>(roles.size());
result.addAll(roles);
List<GrantedAuthority> result = new ArrayList<GrantedAuthority>(roles.size());
result.addAll(roles);
return result;
}
return result;
}
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
if (getGroupSearchBase() == null) {
return new HashSet<GrantedAuthority>();
}
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
if (getGroupSearchBase() == null) {
return new HashSet<GrantedAuthority>();
}
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
if (logger.isDebugEnabled()) {
logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
+ groupSearchFilter + " in search base '" + getGroupSearchBase() + "'");
}
if (logger.isDebugEnabled()) {
logger.debug("Searching for roles for user '" + username + "', DN = " + "'"
+ userDn + "', with filter " + groupSearchFilter
+ " in search base '" + getGroupSearchBase() + "'");
}
Set<String> userRoles = getLdapTemplate().searchForSingleAttributeValues(getGroupSearchBase(), groupSearchFilter,
new String[]{userDn, username}, groupRoleAttribute);
Set<String> userRoles = getLdapTemplate().searchForSingleAttributeValues(
getGroupSearchBase(), groupSearchFilter,
new String[] { userDn, username }, groupRoleAttribute);
if (logger.isDebugEnabled()) {
logger.debug("Roles from search: " + userRoles);
}
if (logger.isDebugEnabled()) {
logger.debug("Roles from search: " + userRoles);
}
for (String role : userRoles) {
for (String role : userRoles) {
if (convertToUpperCase) {
role = role.toUpperCase();
}
if (convertToUpperCase) {
role = role.toUpperCase();
}
authorities.add(new SimpleGrantedAuthority(rolePrefix + role));
}
authorities.add(new SimpleGrantedAuthority(rolePrefix + role));
}
return authorities;
}
return authorities;
}
protected ContextSource getContextSource() {
return getLdapTemplate().getContextSource();
}
protected ContextSource getContextSource() {
return getLdapTemplate().getContextSource();
}
protected String getGroupSearchBase() {
return groupSearchBase;
}
protected String getGroupSearchBase() {
return groupSearchBase;
}
/**
* Convert the role to uppercase
*/
public void setConvertToUpperCase(boolean convertToUpperCase) {
this.convertToUpperCase = convertToUpperCase;
}
/**
* Convert the role to uppercase
*/
public void setConvertToUpperCase(boolean convertToUpperCase) {
this.convertToUpperCase = convertToUpperCase;
}
/**
* The default role which will be assigned to all users.
*
* @param defaultRole the role name, including any desired prefix.
*/
public void setDefaultRole(String defaultRole) {
Assert.notNull(defaultRole, "The defaultRole property cannot be set to null");
this.defaultRole = new SimpleGrantedAuthority(defaultRole);
}
/**
* The default role which will be assigned to all users.
*
* @param defaultRole the role name, including any desired prefix.
*/
public void setDefaultRole(String defaultRole) {
Assert.notNull(defaultRole, "The defaultRole property cannot be set to null");
this.defaultRole = new SimpleGrantedAuthority(defaultRole);
}
public void setGroupRoleAttribute(String groupRoleAttribute) {
Assert.notNull(groupRoleAttribute, "groupRoleAttribute must not be null");
this.groupRoleAttribute = groupRoleAttribute;
}
public void setGroupRoleAttribute(String groupRoleAttribute) {
Assert.notNull(groupRoleAttribute, "groupRoleAttribute must not be null");
this.groupRoleAttribute = groupRoleAttribute;
}
public void setGroupSearchFilter(String groupSearchFilter) {
Assert.notNull(groupSearchFilter, "groupSearchFilter must not be null");
this.groupSearchFilter = groupSearchFilter;
}
public void setGroupSearchFilter(String groupSearchFilter) {
Assert.notNull(groupSearchFilter, "groupSearchFilter must not be null");
this.groupSearchFilter = groupSearchFilter;
}
/**
* Sets the prefix which will be prepended to the values loaded from the directory.
* Defaults to "ROLE_" for compatibility with <tt>RoleVoter/tt>.
*/
public void setRolePrefix(String rolePrefix) {
Assert.notNull(rolePrefix, "rolePrefix must not be null");
this.rolePrefix = rolePrefix;
}
/**
* Sets the prefix which will be prepended to the values loaded from the directory.
* Defaults to "ROLE_" for compatibility with <tt>RoleVoter/tt>.
*/
public void setRolePrefix(String rolePrefix) {
Assert.notNull(rolePrefix, "rolePrefix must not be null");
this.rolePrefix = rolePrefix;
}
/**
* If set to true, a subtree scope search will be performed. If false a single-level search is used.
*
* @param searchSubtree set to true to enable searching of the entire tree below the <tt>groupSearchBase</tt>.
*/
public void setSearchSubtree(boolean searchSubtree) {
int searchScope = searchSubtree ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE;
searchControls.setSearchScope(searchScope);
}
/**
* If set to true, a subtree scope search will be performed. If false a single-level
* search is used.
*
* @param searchSubtree set to true to enable searching of the entire tree below the
* <tt>groupSearchBase</tt>.
*/
public void setSearchSubtree(boolean searchSubtree) {
int searchScope = searchSubtree ? SearchControls.SUBTREE_SCOPE
: SearchControls.ONELEVEL_SCOPE;
searchControls.setSearchScope(searchScope);
}
/**
* Sets the corresponding property on the underlying template, avoiding specific issues with Active Directory.
*
* @see LdapTemplate#setIgnoreNameNotFoundException(boolean)
*/
public void setIgnorePartialResultException(boolean ignore) {
getLdapTemplate().setIgnorePartialResultException(ignore);
}
/**
* Sets the corresponding property on the underlying template, avoiding specific
* issues with Active Directory.
*
* @see LdapTemplate#setIgnoreNameNotFoundException(boolean)
*/
public void setIgnorePartialResultException(boolean ignore) {
getLdapTemplate().setIgnorePartialResultException(ignore);
}
/**
* Returns the current LDAP template.
* Method available so that classes extending this can override the template used
* @return the LDAP template
* @see {@link org.springframework.security.ldap.SpringSecurityLdapTemplate}
*/
protected SpringSecurityLdapTemplate getLdapTemplate() {
return ldapTemplate;
}
/**
* Returns the current LDAP template. Method available so that classes extending this
* can override the template used
* @return the LDAP template
* @see {@link org.springframework.security.ldap.SpringSecurityLdapTemplate}
*/
protected SpringSecurityLdapTemplate getLdapTemplate() {
return ldapTemplate;
}
/**
* Returns the attribute name of the LDAP attribute that will be mapped to the role name
* Method available so that classes extending this can override
* @return the attribute name used for role mapping
* @see {@link #setGroupRoleAttribute(String)}
*/
protected final String getGroupRoleAttribute() {
return groupRoleAttribute;
}
/**
* Returns the attribute name of the LDAP attribute that will be mapped to the role
* name Method available so that classes extending this can override
* @return the attribute name used for role mapping
* @see {@link #setGroupRoleAttribute(String)}
*/
protected final String getGroupRoleAttribute() {
return groupRoleAttribute;
}
/**
* Returns the search filter configured for this populator
* Method available so that classes extending this can override
* @return the search filter
* @see {@link #setGroupSearchFilter(String)}
*/
protected final String getGroupSearchFilter() {
return groupSearchFilter;
}
/**
* Returns the search filter configured for this populator Method available so that
* classes extending this can override
* @return the search filter
* @see {@link #setGroupSearchFilter(String)}
*/
protected final String getGroupSearchFilter() {
return groupSearchFilter;
}
/**
* Returns the role prefix used by this populator
* Method available so that classes extending this can override
* @return the role prefix
* @see {@link #setRolePrefix(String)}
*/
protected final String getRolePrefix() {
return rolePrefix;
}
/**
* Returns the role prefix used by this populator Method available so that classes
* extending this can override
* @return the role prefix
* @see {@link #setRolePrefix(String)}
*/
protected final String getRolePrefix() {
return rolePrefix;
}
/**
* Returns true if role names are converted to uppercase
* Method available so that classes extending this can override
* @return true if role names are converted to uppercase.
* @see {@link #setConvertToUpperCase(boolean)}
*/
protected final boolean isConvertToUpperCase() {
return convertToUpperCase;
}
/**
* Returns true if role names are converted to uppercase Method available so that
* classes extending this can override
* @return true if role names are converted to uppercase.
* @see {@link #setConvertToUpperCase(boolean)}
*/
protected final boolean isConvertToUpperCase() {
return convertToUpperCase;
}
/**
* Returns the default role
* Method available so that classes extending this can override
* @return the default role used
* @see {@link #setDefaultRole(String)}
*/
private GrantedAuthority getDefaultRole() {
return defaultRole;
}
/**
* Returns the default role Method available so that classes extending this can
* override
* @return the default role used
* @see {@link #setDefaultRole(String)}
*/
private GrantedAuthority getDefaultRole() {
return defaultRole;
}
/**
* Returns the search controls
* Method available so that classes extending this can override the search controls used
* @return the search controls
*/
private SearchControls getSearchControls() {
return searchControls;
}
/**
* Returns the search controls Method available so that classes extending this can
* override the search controls used
* @return the search controls
*/
private SearchControls getSearchControls() {
return searchControls;
}
}
@@ -18,10 +18,9 @@ import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.SpringSecurityCoreVersion;
/**
* UserDetails implementation whose properties are based on a subset of the
* LDAP schema for <tt>inetOrgPerson</tt>.
* UserDetails implementation whose properties are based on a subset of the LDAP schema
* for <tt>inetOrgPerson</tt>.
*
* <p>
* The username will be mapped from the <tt>uid</tt> attribute by default.
@@ -30,251 +29,252 @@ import org.springframework.security.core.SpringSecurityCoreVersion;
*/
public class InetOrgPerson extends Person {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private String carLicense;
// Person.cn
private String destinationIndicator;
private String departmentNumber;
// Person.description
private String displayName;
private String employeeNumber;
private String homePhone;
private String homePostalAddress;
private String initials;
private String mail;
private String mobile;
private String o;
private String ou;
private String postalAddress;
private String postalCode;
private String roomNumber;
private String street;
// Person.sn
// Person.telephoneNumber
private String title;
private String uid;
private String carLicense;
// Person.cn
private String destinationIndicator;
private String departmentNumber;
// Person.description
private String displayName;
private String employeeNumber;
private String homePhone;
private String homePostalAddress;
private String initials;
private String mail;
private String mobile;
private String o;
private String ou;
private String postalAddress;
private String postalCode;
private String roomNumber;
private String street;
// Person.sn
// Person.telephoneNumber
private String title;
private String uid;
public String getUid() {
return uid;
}
public String getUid() {
return uid;
}
public String getMail() {
return mail;
}
public String getMail() {
return mail;
}
public String getEmployeeNumber() {
return employeeNumber;
}
public String getEmployeeNumber() {
return employeeNumber;
}
public String getInitials() {
return initials;
}
public String getInitials() {
return initials;
}
public String getDestinationIndicator() {
return destinationIndicator;
}
public String getDestinationIndicator() {
return destinationIndicator;
}
public String getO() {
return o;
}
public String getO() {
return o;
}
public String getOu() {
return ou;
}
public String getOu() {
return ou;
}
public String getTitle() {
return title;
}
public String getTitle() {
return title;
}
public String getCarLicense() {
return carLicense;
}
public String getCarLicense() {
return carLicense;
}
public String getDepartmentNumber() {
return departmentNumber;
}
public String getDepartmentNumber() {
return departmentNumber;
}
public String getDisplayName() {
return displayName;
}
public String getDisplayName() {
return displayName;
}
public String getHomePhone() {
return homePhone;
}
public String getHomePhone() {
return homePhone;
}
public String getRoomNumber() {
return roomNumber;
}
public String getRoomNumber() {
return roomNumber;
}
public String getHomePostalAddress() {
return homePostalAddress;
}
public String getHomePostalAddress() {
return homePostalAddress;
}
public String getMobile() {
return mobile;
}
public String getMobile() {
return mobile;
}
public String getPostalAddress() {
return postalAddress;
}
public String getPostalAddress() {
return postalAddress;
}
public String getPostalCode() {
return postalCode;
}
public String getPostalCode() {
return postalCode;
}
public String getStreet() {
return street;
}
public String getStreet() {
return street;
}
protected void populateContext(DirContextAdapter adapter) {
super.populateContext(adapter);
adapter.setAttributeValue("carLicense", carLicense);
adapter.setAttributeValue("departmentNumber", departmentNumber);
adapter.setAttributeValue("destinationIndicator", destinationIndicator);
adapter.setAttributeValue("displayName", displayName);
adapter.setAttributeValue("employeeNumber", employeeNumber);
adapter.setAttributeValue("homePhone", homePhone);
adapter.setAttributeValue("homePostalAddress", homePostalAddress);
adapter.setAttributeValue("initials", initials);
adapter.setAttributeValue("mail", mail);
adapter.setAttributeValue("mobile", mobile);
adapter.setAttributeValue("postalAddress", postalAddress);
adapter.setAttributeValue("postalCode", postalCode);
adapter.setAttributeValue("ou", ou);
adapter.setAttributeValue("o", o);
adapter.setAttributeValue("roomNumber", roomNumber);
adapter.setAttributeValue("street", street);
adapter.setAttributeValue("uid", uid);
adapter.setAttributeValues("objectclass", new String[] {"top", "person", "organizationalPerson", "inetOrgPerson"});
}
protected void populateContext(DirContextAdapter adapter) {
super.populateContext(adapter);
adapter.setAttributeValue("carLicense", carLicense);
adapter.setAttributeValue("departmentNumber", departmentNumber);
adapter.setAttributeValue("destinationIndicator", destinationIndicator);
adapter.setAttributeValue("displayName", displayName);
adapter.setAttributeValue("employeeNumber", employeeNumber);
adapter.setAttributeValue("homePhone", homePhone);
adapter.setAttributeValue("homePostalAddress", homePostalAddress);
adapter.setAttributeValue("initials", initials);
adapter.setAttributeValue("mail", mail);
adapter.setAttributeValue("mobile", mobile);
adapter.setAttributeValue("postalAddress", postalAddress);
adapter.setAttributeValue("postalCode", postalCode);
adapter.setAttributeValue("ou", ou);
adapter.setAttributeValue("o", o);
adapter.setAttributeValue("roomNumber", roomNumber);
adapter.setAttributeValue("street", street);
adapter.setAttributeValue("uid", uid);
adapter.setAttributeValues("objectclass", new String[] { "top", "person",
"organizationalPerson", "inetOrgPerson" });
}
public static class Essence extends Person.Essence {
public Essence() {
}
public static class Essence extends Person.Essence {
public Essence() {
}
public Essence(InetOrgPerson copyMe) {
super(copyMe);
setCarLicense(copyMe.getCarLicense());
setDepartmentNumber(copyMe.getDepartmentNumber());
setDestinationIndicator(copyMe.getDestinationIndicator());
setDisplayName(copyMe.getDisplayName());
setEmployeeNumber(copyMe.getEmployeeNumber());
setHomePhone(copyMe.getHomePhone());
setHomePostalAddress(copyMe.getHomePostalAddress());
setInitials(copyMe.getInitials());
setMail(copyMe.getMail());
setMobile(copyMe.getMobile());
setO(copyMe.getO());
setOu(copyMe.getOu());
setPostalAddress(copyMe.getPostalAddress());
setPostalCode(copyMe.getPostalCode());
setRoomNumber(copyMe.getRoomNumber());
setStreet(copyMe.getStreet());
setTitle(copyMe.getTitle());
setUid(copyMe.getUid());
}
public Essence(InetOrgPerson copyMe) {
super(copyMe);
setCarLicense(copyMe.getCarLicense());
setDepartmentNumber(copyMe.getDepartmentNumber());
setDestinationIndicator(copyMe.getDestinationIndicator());
setDisplayName(copyMe.getDisplayName());
setEmployeeNumber(copyMe.getEmployeeNumber());
setHomePhone(copyMe.getHomePhone());
setHomePostalAddress(copyMe.getHomePostalAddress());
setInitials(copyMe.getInitials());
setMail(copyMe.getMail());
setMobile(copyMe.getMobile());
setO(copyMe.getO());
setOu(copyMe.getOu());
setPostalAddress(copyMe.getPostalAddress());
setPostalCode(copyMe.getPostalCode());
setRoomNumber(copyMe.getRoomNumber());
setStreet(copyMe.getStreet());
setTitle(copyMe.getTitle());
setUid(copyMe.getUid());
}
public Essence(DirContextOperations ctx) {
super(ctx);
setCarLicense(ctx.getStringAttribute("carLicense"));
setDepartmentNumber(ctx.getStringAttribute("departmentNumber"));
setDestinationIndicator(ctx.getStringAttribute("destinationIndicator"));
setDisplayName(ctx.getStringAttribute("displayName"));
setEmployeeNumber(ctx.getStringAttribute("employeeNumber"));
setHomePhone(ctx.getStringAttribute("homePhone"));
setHomePostalAddress(ctx.getStringAttribute("homePostalAddress"));
setInitials(ctx.getStringAttribute("initials"));
setMail(ctx.getStringAttribute("mail"));
setMobile(ctx.getStringAttribute("mobile"));
setO(ctx.getStringAttribute("o"));
setOu(ctx.getStringAttribute("ou"));
setPostalAddress(ctx.getStringAttribute("postalAddress"));
setPostalCode(ctx.getStringAttribute("postalCode"));
setRoomNumber(ctx.getStringAttribute("roomNumber"));
setStreet(ctx.getStringAttribute("street"));
setTitle(ctx.getStringAttribute("title"));
setUid(ctx.getStringAttribute("uid"));
}
public Essence(DirContextOperations ctx) {
super(ctx);
setCarLicense(ctx.getStringAttribute("carLicense"));
setDepartmentNumber(ctx.getStringAttribute("departmentNumber"));
setDestinationIndicator(ctx.getStringAttribute("destinationIndicator"));
setDisplayName(ctx.getStringAttribute("displayName"));
setEmployeeNumber(ctx.getStringAttribute("employeeNumber"));
setHomePhone(ctx.getStringAttribute("homePhone"));
setHomePostalAddress(ctx.getStringAttribute("homePostalAddress"));
setInitials(ctx.getStringAttribute("initials"));
setMail(ctx.getStringAttribute("mail"));
setMobile(ctx.getStringAttribute("mobile"));
setO(ctx.getStringAttribute("o"));
setOu(ctx.getStringAttribute("ou"));
setPostalAddress(ctx.getStringAttribute("postalAddress"));
setPostalCode(ctx.getStringAttribute("postalCode"));
setRoomNumber(ctx.getStringAttribute("roomNumber"));
setStreet(ctx.getStringAttribute("street"));
setTitle(ctx.getStringAttribute("title"));
setUid(ctx.getStringAttribute("uid"));
}
protected LdapUserDetailsImpl createTarget() {
return new InetOrgPerson();
}
protected LdapUserDetailsImpl createTarget() {
return new InetOrgPerson();
}
public void setMail(String email) {
((InetOrgPerson) instance).mail = email;
}
public void setMail(String email) {
((InetOrgPerson) instance).mail = email;
}
public void setUid(String uid) {
((InetOrgPerson) instance).uid = uid;
public void setUid(String uid) {
((InetOrgPerson) instance).uid = uid;
if(instance.getUsername() == null) {
setUsername(uid);
}
}
if (instance.getUsername() == null) {
setUsername(uid);
}
}
public void setInitials(String initials) {
((InetOrgPerson) instance).initials = initials;
}
public void setInitials(String initials) {
((InetOrgPerson) instance).initials = initials;
}
public void setO(String organization) {
((InetOrgPerson) instance).o = organization;
}
public void setO(String organization) {
((InetOrgPerson) instance).o = organization;
}
public void setOu(String ou) {
((InetOrgPerson) instance).ou = ou;
}
public void setOu(String ou) {
((InetOrgPerson) instance).ou = ou;
}
public void setRoomNumber(String no) {
((InetOrgPerson) instance).roomNumber = no;
}
public void setRoomNumber(String no) {
((InetOrgPerson) instance).roomNumber = no;
}
public void setTitle(String title) {
((InetOrgPerson) instance).title = title;
}
public void setTitle(String title) {
((InetOrgPerson) instance).title = title;
}
public void setCarLicense(String carLicense) {
((InetOrgPerson) instance).carLicense = carLicense;
}
public void setCarLicense(String carLicense) {
((InetOrgPerson) instance).carLicense = carLicense;
}
public void setDepartmentNumber(String departmentNumber) {
((InetOrgPerson) instance).departmentNumber = departmentNumber;
}
public void setDepartmentNumber(String departmentNumber) {
((InetOrgPerson) instance).departmentNumber = departmentNumber;
}
public void setDisplayName(String displayName) {
((InetOrgPerson) instance).displayName = displayName;
}
public void setDisplayName(String displayName) {
((InetOrgPerson) instance).displayName = displayName;
}
public void setEmployeeNumber(String no) {
((InetOrgPerson) instance).employeeNumber = no;
}
public void setEmployeeNumber(String no) {
((InetOrgPerson) instance).employeeNumber = no;
}
public void setDestinationIndicator(String destination) {
((InetOrgPerson) instance).destinationIndicator = destination;
}
public void setDestinationIndicator(String destination) {
((InetOrgPerson) instance).destinationIndicator = destination;
}
public void setHomePhone(String homePhone) {
((InetOrgPerson) instance).homePhone = homePhone;
}
public void setHomePhone(String homePhone) {
((InetOrgPerson) instance).homePhone = homePhone;
}
public void setStreet(String street) {
((InetOrgPerson) instance).street = street;
}
public void setStreet(String street) {
((InetOrgPerson) instance).street = street;
}
public void setPostalCode(String postalCode) {
((InetOrgPerson) instance).postalCode = postalCode;
}
public void setPostalCode(String postalCode) {
((InetOrgPerson) instance).postalCode = postalCode;
}
public void setPostalAddress(String postalAddress) {
((InetOrgPerson) instance).postalAddress = postalAddress;
}
public void setPostalAddress(String postalAddress) {
((InetOrgPerson) instance).postalAddress = postalAddress;
}
public void setMobile(String mobile) {
((InetOrgPerson) instance).mobile = mobile;
}
public void setMobile(String mobile) {
((InetOrgPerson) instance).mobile = mobile;
}
public void setHomePostalAddress(String homePostalAddress) {
((InetOrgPerson) instance).homePostalAddress = homePostalAddress;
}
}
public void setHomePostalAddress(String homePostalAddress) {
((InetOrgPerson) instance).homePostalAddress = homePostalAddress;
}
}
}
@@ -22,26 +22,27 @@ import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.util.Assert;
/**
* @author Luke Taylor
*/
public class InetOrgPersonContextMapper implements UserDetailsContextMapper {
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx);
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx);
p.setUsername(username);
p.setAuthorities(authorities);
p.setUsername(username);
p.setAuthorities(authorities);
return p.createUserDetails();
return p.createUserDetails();
}
}
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
Assert.isInstanceOf(InetOrgPerson.class, user, "UserDetails must be an InetOrgPerson instance");
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
Assert.isInstanceOf(InetOrgPerson.class, user,
"UserDetails must be an InetOrgPerson instance");
InetOrgPerson p = (InetOrgPerson) user;
p.populateContext(ctx);
}
InetOrgPerson p = (InetOrgPerson) user;
p.populateContext(ctx);
}
}
@@ -21,26 +21,27 @@ import org.springframework.security.core.GrantedAuthority;
import org.springframework.ldap.core.DirContextOperations;
/**
* Obtains a list of granted authorities for an Ldap user.
* <p>
* Used by the <tt>LdapAuthenticationProvider</tt> once a user has been
* authenticated to create the final user details object.
* Used by the <tt>LdapAuthenticationProvider</tt> once a user has been authenticated to
* create the final user details object.
* </p>
*
* @author Luke Taylor
*/
public interface LdapAuthoritiesPopulator {
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* Get the list of authorities for the user.
*
* @param userData the context object which was returned by the LDAP authenticator.
*
* @return the granted authorities for the given user.
*
*/
Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username);
/**
* Get the list of authorities for the user.
*
* @param userData the context object which was returned by the LDAP authenticator.
*
* @return the granted authorities for the given user.
*
*/
Collection<? extends GrantedAuthority> getGrantedAuthorities(
DirContextOperations userData, String username);
}
@@ -23,133 +23,131 @@ import java.util.List;
import java.util.Map;
/**
* An authority that contains at least a DN and a role name for an LDAP entry but can also contain other desired
* attributes to be fetched during an LDAP authority search.
* An authority that contains at least a DN and a role name for an LDAP entry but can also
* contain other desired attributes to be fetched during an LDAP authority search.
*
* @author Filip Hanik
*/
public class LdapAuthority implements GrantedAuthority {
private String dn;
private String role;
private Map<String, List<String>> attributes;
private String dn;
private String role;
private Map<String, List<String>> attributes;
/**
* Constructs an LdapAuthority that has a role and a DN but no other attributes
*
* @param role
* @param dn
*/
public LdapAuthority(String role, String dn) {
this(role, dn, null);
}
/**
* Constructs an LdapAuthority that has a role and a DN but no other attributes
*
* @param role
* @param dn
*/
public LdapAuthority(String role, String dn) {
this(role, dn, null);
}
/**
* Constructs an LdapAuthority with the given role, DN and other LDAP attributes
*
* @param role
* @param dn
* @param attributes
*/
public LdapAuthority(String role, String dn, Map<String, List<String>> attributes) {
Assert.notNull(role, "role can not be null");
Assert.notNull(dn, "dn can not be null");
/**
* Constructs an LdapAuthority with the given role, DN and other LDAP attributes
*
* @param role
* @param dn
* @param attributes
*/
public LdapAuthority(String role, String dn, Map<String, List<String>> attributes) {
Assert.notNull(role, "role can not be null");
Assert.notNull(dn, "dn can not be null");
this.role = role;
this.dn = dn;
this.attributes = attributes;
}
this.role = role;
this.dn = dn;
this.attributes = attributes;
}
/**
* Returns the LDAP attributes
*
* @return the LDAP attributes, map can be null
*/
public Map<String, List<String>> getAttributes() {
return attributes;
}
/**
* Returns the LDAP attributes
*
* @return the LDAP attributes, map can be null
*/
public Map<String, List<String>> getAttributes() {
return attributes;
}
/**
* Returns the DN for this LDAP authority
*
* @return
*/
public String getDn() {
return dn;
}
/**
* Returns the DN for this LDAP authority
*
* @return
*/
public String getDn() {
return dn;
}
/**
* Returns the values for a specific attribute
*
* @param name the attribute name
* @return a String array, never null but may be zero length
*/
public List<String> getAttributeValues(String name) {
List<String> result = null;
if (attributes != null) {
result = attributes.get(name);
}
if (result == null) {
result = Collections.emptyList();
}
return result;
}
/**
* Returns the values for a specific attribute
*
* @param name the attribute name
* @return a String array, never null but may be zero length
*/
public List<String> getAttributeValues(String name) {
List<String> result = null;
if (attributes != null) {
result = attributes.get(name);
}
if (result == null) {
result = Collections.emptyList();
}
return result;
}
/**
* Returns the first attribute value for a specified attribute
*
* @param name
* @return the first attribute value for a specified attribute, may be null
*/
public String getFirstAttributeValue(String name) {
List<String> result = getAttributeValues(name);
if (result.isEmpty()) {
return null;
}
else {
return result.get(0);
}
}
/**
* Returns the first attribute value for a specified attribute
*
* @param name
* @return the first attribute value for a specified attribute, may be null
*/
public String getFirstAttributeValue(String name) {
List<String> result = getAttributeValues(name);
if (result.isEmpty()) {
return null;
} else {
return result.get(0);
}
}
/**
* {@inheritDoc}
*/
public String getAuthority() {
return role;
}
/**
* {@inheritDoc}
*/
public String getAuthority() {
return role;
}
/**
* Compares the LdapAuthority based on {@link #getAuthority()} and {@link #getDn()}
* values {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof LdapAuthority)) {
return false;
}
/**
* Compares the LdapAuthority based on {@link #getAuthority()} and {@link #getDn()} values {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof LdapAuthority)) {
return false;
}
LdapAuthority that = (LdapAuthority) o;
LdapAuthority that = (LdapAuthority) o;
if (!dn.equals(that.dn)) {
return false;
}
return role.equals(that.role);
}
if (!dn.equals(that.dn)) {
return false;
}
return role.equals(that.role);
}
@Override
public int hashCode() {
int result = dn.hashCode();
result = 31 * result + (role != null ? role.hashCode() : 0);
return result;
}
@Override
public int hashCode() {
int result = dn.hashCode();
result = 31 * result + (role != null ? role.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "LdapAuthority{" +
"dn='" + dn + '\'' +
", role='" + role + '\'' +
'}';
}
@Override
public String toString() {
return "LdapAuthority{" + "dn='" + dn + '\'' + ", role='" + role + '\'' + '}';
}
}
@@ -23,12 +23,13 @@ import org.springframework.security.core.userdetails.UserDetails;
* @author Luke Taylor
*/
public interface LdapUserDetails extends UserDetails {
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* The DN of the entry for this user's account.
*
* @return the user's DN
*/
String getDn();
/**
* The DN of the entry for this user's account.
*
* @return the user's DN
*/
String getDn();
}
@@ -29,237 +29,251 @@ import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.ldap.ppolicy.PasswordPolicyData;
import org.springframework.util.Assert;
/**
* A UserDetails implementation which is used internally by the Ldap services. It also contains the user's
* distinguished name and a set of attributes that have been retrieved from the Ldap server.
* A UserDetails implementation which is used internally by the Ldap services. It also
* contains the user's distinguished name and a set of attributes that have been retrieved
* from the Ldap server.
* <p>
* An instance may be created as the result of a search, or when user information is retrieved during authentication.
* An instance may be created as the result of a search, or when user information is
* retrieved during authentication.
* <p>
* An instance of this class will be used by the <tt>LdapAuthenticationProvider</tt> to construct the final user details
* object that it returns.
* An instance of this class will be used by the <tt>LdapAuthenticationProvider</tt> to
* construct the final user details object that it returns.
* <p>
* The {@code equals} and {@code hashcode} methods are implemented using the {@code Dn} property and do not consider
* additional state, so it is not possible two store two instances with the same DN in the same set, or use them as
* keys in a map.
* The {@code equals} and {@code hashcode} methods are implemented using the {@code Dn}
* property and do not consider additional state, so it is not possible two store two
* instances with the same DN in the same set, or use them as keys in a map.
*
* @author Luke Taylor
*/
public class LdapUserDetailsImpl implements LdapUserDetails, PasswordPolicyData {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private String dn;
private String password;
private String username;
private Collection<GrantedAuthority> authorities = AuthorityUtils.NO_AUTHORITIES;
private boolean accountNonExpired = true;
private boolean accountNonLocked = true;
private boolean credentialsNonExpired = true;
private boolean enabled = true;
// PPolicy data
private int timeBeforeExpiration = Integer.MAX_VALUE;
private int graceLoginsRemaining = Integer.MAX_VALUE;
private String dn;
private String password;
private String username;
private Collection<GrantedAuthority> authorities = AuthorityUtils.NO_AUTHORITIES;
private boolean accountNonExpired = true;
private boolean accountNonLocked = true;
private boolean credentialsNonExpired = true;
private boolean enabled = true;
// PPolicy data
private int timeBeforeExpiration = Integer.MAX_VALUE;
private int graceLoginsRemaining = Integer.MAX_VALUE;
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
protected LdapUserDetailsImpl() {}
protected LdapUserDetailsImpl() {
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
public Collection<GrantedAuthority> getAuthorities() {
return authorities;
}
public Collection<GrantedAuthority> getAuthorities() {
return authorities;
}
public String getDn() {
return dn;
}
public String getDn() {
return dn;
}
public String getPassword() {
return password;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public String getUsername() {
return username;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public boolean isEnabled() {
return enabled;
}
public boolean isEnabled() {
return enabled;
}
public int getTimeBeforeExpiration() {
return timeBeforeExpiration;
}
public int getTimeBeforeExpiration() {
return timeBeforeExpiration;
}
public int getGraceLoginsRemaining() {
return graceLoginsRemaining;
}
public int getGraceLoginsRemaining() {
return graceLoginsRemaining;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LdapUserDetailsImpl) {
return dn.equals(((LdapUserDetailsImpl)obj).dn);
}
return false;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LdapUserDetailsImpl) {
return dn.equals(((LdapUserDetailsImpl) obj).dn);
}
return false;
}
@Override
public int hashCode() {
return dn.hashCode();
}
@Override
public int hashCode() {
return dn.hashCode();
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString()).append(": ");
sb.append("Dn: ").append(dn).append("; ");
sb.append("Username: ").append(this.username).append("; ");
sb.append("Password: [PROTECTED]; ");
sb.append("Enabled: ").append(this.enabled).append("; ");
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
sb.append("CredentialsNonExpired: ").append(this.credentialsNonExpired).append("; ");
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString()).append(": ");
sb.append("Dn: ").append(dn).append("; ");
sb.append("Username: ").append(this.username).append("; ");
sb.append("Password: [PROTECTED]; ");
sb.append("Enabled: ").append(this.enabled).append("; ");
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
sb.append("CredentialsNonExpired: ").append(this.credentialsNonExpired)
.append("; ");
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
if (this.getAuthorities() != null && !this.getAuthorities().isEmpty()) {
sb.append("Granted Authorities: ");
boolean first = true;
if (this.getAuthorities() != null && !this.getAuthorities().isEmpty()) {
sb.append("Granted Authorities: ");
boolean first = true;
for (Object authority : this.getAuthorities()) {
if (first) {
first = false;
} else {
sb.append(", ");
}
for (Object authority : this.getAuthorities()) {
if (first) {
first = false;
}
else {
sb.append(", ");
}
sb.append(authority.toString());
}
} else {
sb.append("Not granted any authorities");
}
sb.append(authority.toString());
}
}
else {
sb.append("Not granted any authorities");
}
return sb.toString();
}
return sb.toString();
}
//~ Inner Classes ==================================================================================================
// ~ Inner Classes
// ==================================================================================================
/**
* Variation of essence pattern. Used to create mutable intermediate object
*/
public static class Essence {
protected LdapUserDetailsImpl instance = createTarget();
private List<GrantedAuthority> mutableAuthorities = new ArrayList<GrantedAuthority>();
/**
* Variation of essence pattern. Used to create mutable intermediate object
*/
public static class Essence {
protected LdapUserDetailsImpl instance = createTarget();
private List<GrantedAuthority> mutableAuthorities = new ArrayList<GrantedAuthority>();
public Essence() { }
public Essence() {
}
public Essence(DirContextOperations ctx) {
setDn(ctx.getDn());
}
public Essence(DirContextOperations ctx) {
setDn(ctx.getDn());
}
public Essence(LdapUserDetails copyMe) {
setDn(copyMe.getDn());
setUsername(copyMe.getUsername());
setPassword(copyMe.getPassword());
setEnabled(copyMe.isEnabled());
setAccountNonExpired(copyMe.isAccountNonExpired());
setCredentialsNonExpired(copyMe.isCredentialsNonExpired());
setAccountNonLocked(copyMe.isAccountNonLocked());
setAuthorities(copyMe.getAuthorities());
}
public Essence(LdapUserDetails copyMe) {
setDn(copyMe.getDn());
setUsername(copyMe.getUsername());
setPassword(copyMe.getPassword());
setEnabled(copyMe.isEnabled());
setAccountNonExpired(copyMe.isAccountNonExpired());
setCredentialsNonExpired(copyMe.isCredentialsNonExpired());
setAccountNonLocked(copyMe.isAccountNonLocked());
setAuthorities(copyMe.getAuthorities());
}
protected LdapUserDetailsImpl createTarget() {
return new LdapUserDetailsImpl();
}
protected LdapUserDetailsImpl createTarget() {
return new LdapUserDetailsImpl();
}
/** Adds the authority to the list, unless it is already there, in which case it is ignored */
public void addAuthority(GrantedAuthority a) {
if(!hasAuthority(a)) {
mutableAuthorities.add(a);
}
}
/**
* Adds the authority to the list, unless it is already there, in which case it is
* ignored
*/
public void addAuthority(GrantedAuthority a) {
if (!hasAuthority(a)) {
mutableAuthorities.add(a);
}
}
private boolean hasAuthority(GrantedAuthority a) {
for (GrantedAuthority authority : mutableAuthorities) {
if(authority.equals(a)) {
return true;
}
}
return false;
}
private boolean hasAuthority(GrantedAuthority a) {
for (GrantedAuthority authority : mutableAuthorities) {
if (authority.equals(a)) {
return true;
}
}
return false;
}
public LdapUserDetails createUserDetails() {
Assert.notNull(instance, "Essence can only be used to create a single instance");
Assert.notNull(instance.username, "username must not be null");
Assert.notNull(instance.getDn(), "Distinguished name must not be null");
public LdapUserDetails createUserDetails() {
Assert.notNull(instance,
"Essence can only be used to create a single instance");
Assert.notNull(instance.username, "username must not be null");
Assert.notNull(instance.getDn(), "Distinguished name must not be null");
instance.authorities = Collections.unmodifiableList(mutableAuthorities);
instance.authorities = Collections.unmodifiableList(mutableAuthorities);
LdapUserDetails newInstance = instance;
LdapUserDetails newInstance = instance;
instance = null;
instance = null;
return newInstance;
}
return newInstance;
}
public Collection<GrantedAuthority> getGrantedAuthorities() {
return mutableAuthorities;
}
public Collection<GrantedAuthority> getGrantedAuthorities() {
return mutableAuthorities;
}
public void setAccountNonExpired(boolean accountNonExpired) {
instance.accountNonExpired = accountNonExpired;
}
public void setAccountNonExpired(boolean accountNonExpired) {
instance.accountNonExpired = accountNonExpired;
}
public void setAccountNonLocked(boolean accountNonLocked) {
instance.accountNonLocked = accountNonLocked;
}
public void setAccountNonLocked(boolean accountNonLocked) {
instance.accountNonLocked = accountNonLocked;
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
mutableAuthorities = new ArrayList<GrantedAuthority>();
mutableAuthorities.addAll(authorities);
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
mutableAuthorities = new ArrayList<GrantedAuthority>();
mutableAuthorities.addAll(authorities);
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
instance.credentialsNonExpired = credentialsNonExpired;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
instance.credentialsNonExpired = credentialsNonExpired;
}
public void setDn(String dn) {
instance.dn = dn;
}
public void setDn(String dn) {
instance.dn = dn;
}
public void setDn(Name dn) {
instance.dn = dn.toString();
}
public void setDn(Name dn) {
instance.dn = dn.toString();
}
public void setEnabled(boolean enabled) {
instance.enabled = enabled;
}
public void setEnabled(boolean enabled) {
instance.enabled = enabled;
}
public void setPassword(String password) {
instance.password = password;
}
public void setPassword(String password) {
instance.password = password;
}
public void setUsername(String username) {
instance.username = username;
}
public void setUsername(String username) {
instance.username = username;
}
public void setTimeBeforeExpiration(int timeBeforeExpiration) {
instance.timeBeforeExpiration = timeBeforeExpiration;
}
public void setTimeBeforeExpiration(int timeBeforeExpiration) {
instance.timeBeforeExpiration = timeBeforeExpiration;
}
public void setGraceLoginsRemaining(int graceLoginsRemaining) {
instance.graceLoginsRemaining = graceLoginsRemaining;
}
}
public void setGraceLoginsRemaining(int graceLoginsRemaining) {
instance.graceLoginsRemaining = graceLoginsRemaining;
}
}
}
@@ -59,331 +59,358 @@ import org.springframework.util.Assert;
/**
* An Ldap implementation of UserDetailsManager.
* <p>
* It is designed around a standard setup where users and groups/roles are stored under separate contexts,
* defined by the "userDnBase" and "groupSearchBase" properties respectively.
* It is designed around a standard setup where users and groups/roles are stored under
* separate contexts, defined by the "userDnBase" and "groupSearchBase" properties
* respectively.
* <p>
* In this case, LDAP is being used purely to retrieve information and this class can be used in place of any other
* UserDetailsService for authentication. Authentication isn't performed directly against the directory, unlike with the
* LDAP authentication provider setup.
* In this case, LDAP is being used purely to retrieve information and this class can be
* used in place of any other UserDetailsService for authentication. Authentication isn't
* performed directly against the directory, unlike with the LDAP authentication provider
* setup.
*
* @author Luke Taylor
* @since 2.0
*/
public class LdapUserDetailsManager implements UserDetailsManager {
private final Log logger = LogFactory.getLog(LdapUserDetailsManager.class);
/**
* The strategy for mapping usernames to LDAP distinguished names.
* This will be used when building DNs for creating new users etc.
*/
LdapUsernameToDnMapper usernameMapper = new DefaultLdapUsernameToDnMapper("cn=users", "uid");
/** The DN under which groups are stored */
private DistinguishedName groupSearchBase = new DistinguishedName("cn=groups");
/** Password attribute name */
private String passwordAttributeName = "userPassword";
/** The attribute which corresponds to the role name of a group. */
private String groupRoleAttributeName ="cn";
/** The attribute which contains members of a group */
private String groupMemberAttributeName = "uniquemember";
private final String rolePrefix = "ROLE_";
/** The pattern to be used for the user search. {0} is the user's DN */
private String groupSearchFilter = "(uniquemember={0})";
/**
* The strategy used to create a UserDetails object from the LDAP context, username and list of authorities.
* This should be set to match the required UserDetails implementation.
*/
private UserDetailsContextMapper userDetailsMapper = new InetOrgPersonContextMapper();
private final LdapTemplate template;
/** Default context mapper used to create a set of roles from a list of attributes */
private AttributesMapper roleMapper = new AttributesMapper() {
public Object mapFromAttributes(Attributes attributes) throws NamingException {
Attribute roleAttr = attributes.get(groupRoleAttributeName);
NamingEnumeration<?> ne = roleAttr.getAll();
// assert ne.hasMore();
Object group = ne.next();
String role = group.toString();
return new SimpleGrantedAuthority(rolePrefix + role.toUpperCase());
}
};
private String[] attributesToRetrieve;
public LdapUserDetailsManager(ContextSource contextSource) {
template = new LdapTemplate(contextSource);
}
public UserDetails loadUserByUsername(String username) {
DistinguishedName dn = usernameMapper.buildDn(username);
List<GrantedAuthority> authorities = getUserAuthorities(dn, username);
logger.debug("Loading user '"+ username + "' with DN '" + dn + "'");
DirContextAdapter userCtx = loadUserAsContext(dn, username);
return userDetailsMapper.mapUserFromContext(userCtx, username, authorities);
}
private DirContextAdapter loadUserAsContext(final DistinguishedName dn, final String username) {
return (DirContextAdapter) template.executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
try {
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx));
} catch(NameNotFoundException notFound) {
throw new UsernameNotFoundException("User " + username + " not found", notFound);
}
}
});
}
/**
* Changes the password for the current user. The username is obtained from the security context.
* <p>
* If the old password is supplied, the update will be made by rebinding as the user, thus modifying the password
* using the user's permissions. If <code>oldPassword</code> is null, the update will be attempted using a
* standard read/write context supplied by the context source.
* </p>
*
* @param oldPassword the old password
* @param newPassword the new value of the password.
*/
public void changePassword(final String oldPassword, final String newPassword) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Assert.notNull(authentication,
"No authentication object found in security context. Can't change current user's password!");
String username = authentication.getName();
logger.debug("Changing password for user '"+ username);
final DistinguishedName dn = usernameMapper.buildDn(username);
final ModificationItem[] passwordChange = new ModificationItem[] {
new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(passwordAttributeName, newPassword))
};
if(oldPassword == null) {
template.modifyAttributes(dn, passwordChange);
return;
}
template.executeReadWrite(new ContextExecutor() {
public Object executeWithContext(DirContext dirCtx) throws NamingException {
LdapContext ctx = (LdapContext) dirCtx;
ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, LdapUtils.getFullDn(dn, ctx).toString());
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, oldPassword);
// TODO: reconnect doesn't appear to actually change the credentials
try {
ctx.reconnect(null);
} catch (javax.naming.AuthenticationException e) {
throw new BadCredentialsException("Authentication for password change failed.");
}
ctx.modifyAttributes(dn, passwordChange);
return null;
}
});
}
/**
*
* @param dn the distinguished name of the entry - may be either relative to the base context
* or a complete DN including the name of the context (either is supported).
* @param username the user whose roles are required.
* @return the granted authorities returned by the group search
*/
@SuppressWarnings("unchecked")
List<GrantedAuthority> getUserAuthorities(final DistinguishedName dn, final String username) {
SearchExecutor se = new SearchExecutor() {
public NamingEnumeration<SearchResult> executeSearch(DirContext ctx) throws NamingException {
DistinguishedName fullDn = LdapUtils.getFullDn(dn, ctx);
SearchControls ctrls = new SearchControls();
ctrls.setReturningAttributes(new String[] {groupRoleAttributeName});
return ctx.search(groupSearchBase, groupSearchFilter, new String[] {fullDn.toUrl(), username}, ctrls);
}
};
AttributesMapperCallbackHandler roleCollector =
new AttributesMapperCallbackHandler(roleMapper);
template.search(se, roleCollector);
return roleCollector.getList();
}
public void createUser(UserDetails user) {
DirContextAdapter ctx = new DirContextAdapter();
copyToContext(user, ctx);
DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
logger.debug("Creating new user '"+ user.getUsername() + "' with DN '" + dn + "'");
template.bind(dn, ctx, null);
// Check for any existing authorities which might be set for this DN and remove them
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
if(authorities.size() > 0) {
removeAuthorities(dn, authorities);
}
addAuthorities(dn, user.getAuthorities());
}
public void updateUser(UserDetails user) {
DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
logger.debug("Updating user '"+ user.getUsername() + "' with DN '" + dn + "'");
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
DirContextAdapter ctx = loadUserAsContext(dn, user.getUsername());
ctx.setUpdateMode(true);
copyToContext(user, ctx);
// Remove the objectclass attribute from the list of mods (if present).
List<ModificationItem> mods = new LinkedList<ModificationItem>(Arrays.asList(ctx.getModificationItems()));
ListIterator<ModificationItem> modIt = mods.listIterator();
while(modIt.hasNext()) {
ModificationItem mod = (ModificationItem) modIt.next();
Attribute a = mod.getAttribute();
if("objectclass".equalsIgnoreCase(a.getID())) {
modIt.remove();
}
}
template.modifyAttributes(dn, mods.toArray(new ModificationItem[mods.size()]));
// template.rebind(dn, ctx, null);
// Remove the old authorities and replace them with the new one
removeAuthorities(dn, authorities);
addAuthorities(dn, user.getAuthorities());
}
public void deleteUser(String username) {
DistinguishedName dn = usernameMapper.buildDn(username);
removeAuthorities(dn, getUserAuthorities(dn, username));
template.unbind(dn);
}
public boolean userExists(String username) {
DistinguishedName dn = usernameMapper.buildDn(username);
try {
Object obj = template.lookup(dn);
if (obj instanceof Context) {
LdapUtils.closeContext((Context) obj);
}
return true;
} catch(org.springframework.ldap.NameNotFoundException e) {
return false;
}
}
/**
* Creates a DN from a group name.
*
* @param group the name of the group
* @return the DN of the corresponding group, including the groupSearchBase
*/
protected DistinguishedName buildGroupDn(String group) {
DistinguishedName dn = new DistinguishedName(groupSearchBase);
dn.add(groupRoleAttributeName, group.toLowerCase());
return dn;
}
protected void copyToContext(UserDetails user, DirContextAdapter ctx) {
userDetailsMapper.mapUserToContext(user, ctx);
}
protected void addAuthorities(DistinguishedName userDn, Collection<? extends GrantedAuthority> authorities) {
modifyAuthorities(userDn, authorities, DirContext.ADD_ATTRIBUTE);
}
protected void removeAuthorities(DistinguishedName userDn, Collection<? extends GrantedAuthority> authorities) {
modifyAuthorities(userDn, authorities, DirContext.REMOVE_ATTRIBUTE);
}
private void modifyAuthorities(final DistinguishedName userDn, final Collection<? extends GrantedAuthority> authorities, final int modType) {
template.executeReadWrite(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
for(GrantedAuthority authority : authorities) {
String group = convertAuthorityToGroup(authority);
DistinguishedName fullDn = LdapUtils.getFullDn(userDn, ctx);
ModificationItem addGroup = new ModificationItem(modType,
new BasicAttribute(groupMemberAttributeName, fullDn.toUrl()));
ctx.modifyAttributes(buildGroupDn(group), new ModificationItem[] {addGroup});
}
return null;
}
});
}
private String convertAuthorityToGroup(GrantedAuthority authority) {
String group = authority.getAuthority();
if(group.startsWith(rolePrefix)) {
group = group.substring(rolePrefix.length());
}
return group;
}
public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
this.usernameMapper = usernameMapper;
}
public void setPasswordAttributeName(String passwordAttributeName) {
this.passwordAttributeName = passwordAttributeName;
}
public void setGroupSearchBase(String groupSearchBase) {
this.groupSearchBase = new DistinguishedName(groupSearchBase);
}
public void setGroupRoleAttributeName(String groupRoleAttributeName) {
this.groupRoleAttributeName = groupRoleAttributeName;
}
public void setAttributesToRetrieve(String[] attributesToRetrieve) {
Assert.notNull(attributesToRetrieve);
this.attributesToRetrieve = attributesToRetrieve;
}
public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper) {
this.userDetailsMapper = userDetailsMapper;
}
/**
* Sets the name of the multi-valued attribute which holds the DNs of users who are members of a group.
* <p>
* Usually this will be <tt>uniquemember</tt> (the default value) or <tt>member</tt>.
* </p>
*
* @param groupMemberAttributeName the name of the attribute used to store group members.
*/
public void setGroupMemberAttributeName(String groupMemberAttributeName) {
Assert.hasText(groupMemberAttributeName);
this.groupMemberAttributeName = groupMemberAttributeName;
this.groupSearchFilter = "(" + groupMemberAttributeName + "={0})";
}
public void setRoleMapper(AttributesMapper roleMapper) {
this.roleMapper = roleMapper;
}
private final Log logger = LogFactory.getLog(LdapUserDetailsManager.class);
/**
* The strategy for mapping usernames to LDAP distinguished names. This will be used
* when building DNs for creating new users etc.
*/
LdapUsernameToDnMapper usernameMapper = new DefaultLdapUsernameToDnMapper("cn=users",
"uid");
/** The DN under which groups are stored */
private DistinguishedName groupSearchBase = new DistinguishedName("cn=groups");
/** Password attribute name */
private String passwordAttributeName = "userPassword";
/** The attribute which corresponds to the role name of a group. */
private String groupRoleAttributeName = "cn";
/** The attribute which contains members of a group */
private String groupMemberAttributeName = "uniquemember";
private final String rolePrefix = "ROLE_";
/** The pattern to be used for the user search. {0} is the user's DN */
private String groupSearchFilter = "(uniquemember={0})";
/**
* The strategy used to create a UserDetails object from the LDAP context, username
* and list of authorities. This should be set to match the required UserDetails
* implementation.
*/
private UserDetailsContextMapper userDetailsMapper = new InetOrgPersonContextMapper();
private final LdapTemplate template;
/** Default context mapper used to create a set of roles from a list of attributes */
private AttributesMapper roleMapper = new AttributesMapper() {
public Object mapFromAttributes(Attributes attributes) throws NamingException {
Attribute roleAttr = attributes.get(groupRoleAttributeName);
NamingEnumeration<?> ne = roleAttr.getAll();
// assert ne.hasMore();
Object group = ne.next();
String role = group.toString();
return new SimpleGrantedAuthority(rolePrefix + role.toUpperCase());
}
};
private String[] attributesToRetrieve;
public LdapUserDetailsManager(ContextSource contextSource) {
template = new LdapTemplate(contextSource);
}
public UserDetails loadUserByUsername(String username) {
DistinguishedName dn = usernameMapper.buildDn(username);
List<GrantedAuthority> authorities = getUserAuthorities(dn, username);
logger.debug("Loading user '" + username + "' with DN '" + dn + "'");
DirContextAdapter userCtx = loadUserAsContext(dn, username);
return userDetailsMapper.mapUserFromContext(userCtx, username, authorities);
}
private DirContextAdapter loadUserAsContext(final DistinguishedName dn,
final String username) {
return (DirContextAdapter) template.executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
try {
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx));
}
catch (NameNotFoundException notFound) {
throw new UsernameNotFoundException(
"User " + username + " not found", notFound);
}
}
});
}
/**
* Changes the password for the current user. The username is obtained from the
* security context.
* <p>
* If the old password is supplied, the update will be made by rebinding as the user,
* thus modifying the password using the user's permissions. If
* <code>oldPassword</code> is null, the update will be attempted using a standard
* read/write context supplied by the context source.
* </p>
*
* @param oldPassword the old password
* @param newPassword the new value of the password.
*/
public void changePassword(final String oldPassword, final String newPassword) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
Assert.notNull(
authentication,
"No authentication object found in security context. Can't change current user's password!");
String username = authentication.getName();
logger.debug("Changing password for user '" + username);
final DistinguishedName dn = usernameMapper.buildDn(username);
final ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(
DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(passwordAttributeName,
newPassword)) };
if (oldPassword == null) {
template.modifyAttributes(dn, passwordChange);
return;
}
template.executeReadWrite(new ContextExecutor() {
public Object executeWithContext(DirContext dirCtx) throws NamingException {
LdapContext ctx = (LdapContext) dirCtx;
ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL,
LdapUtils.getFullDn(dn, ctx).toString());
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, oldPassword);
// TODO: reconnect doesn't appear to actually change the credentials
try {
ctx.reconnect(null);
}
catch (javax.naming.AuthenticationException e) {
throw new BadCredentialsException(
"Authentication for password change failed.");
}
ctx.modifyAttributes(dn, passwordChange);
return null;
}
});
}
/**
*
* @param dn the distinguished name of the entry - may be either relative to the base
* context or a complete DN including the name of the context (either is supported).
* @param username the user whose roles are required.
* @return the granted authorities returned by the group search
*/
@SuppressWarnings("unchecked")
List<GrantedAuthority> getUserAuthorities(final DistinguishedName dn,
final String username) {
SearchExecutor se = new SearchExecutor() {
public NamingEnumeration<SearchResult> executeSearch(DirContext ctx)
throws NamingException {
DistinguishedName fullDn = LdapUtils.getFullDn(dn, ctx);
SearchControls ctrls = new SearchControls();
ctrls.setReturningAttributes(new String[] { groupRoleAttributeName });
return ctx.search(groupSearchBase, groupSearchFilter, new String[] {
fullDn.toUrl(), username }, ctrls);
}
};
AttributesMapperCallbackHandler roleCollector = new AttributesMapperCallbackHandler(
roleMapper);
template.search(se, roleCollector);
return roleCollector.getList();
}
public void createUser(UserDetails user) {
DirContextAdapter ctx = new DirContextAdapter();
copyToContext(user, ctx);
DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
logger.debug("Creating new user '" + user.getUsername() + "' with DN '" + dn
+ "'");
template.bind(dn, ctx, null);
// Check for any existing authorities which might be set for this DN and remove
// them
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
if (authorities.size() > 0) {
removeAuthorities(dn, authorities);
}
addAuthorities(dn, user.getAuthorities());
}
public void updateUser(UserDetails user) {
DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
logger.debug("Updating user '" + user.getUsername() + "' with DN '" + dn + "'");
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
DirContextAdapter ctx = loadUserAsContext(dn, user.getUsername());
ctx.setUpdateMode(true);
copyToContext(user, ctx);
// Remove the objectclass attribute from the list of mods (if present).
List<ModificationItem> mods = new LinkedList<ModificationItem>(Arrays.asList(ctx
.getModificationItems()));
ListIterator<ModificationItem> modIt = mods.listIterator();
while (modIt.hasNext()) {
ModificationItem mod = (ModificationItem) modIt.next();
Attribute a = mod.getAttribute();
if ("objectclass".equalsIgnoreCase(a.getID())) {
modIt.remove();
}
}
template.modifyAttributes(dn, mods.toArray(new ModificationItem[mods.size()]));
// template.rebind(dn, ctx, null);
// Remove the old authorities and replace them with the new one
removeAuthorities(dn, authorities);
addAuthorities(dn, user.getAuthorities());
}
public void deleteUser(String username) {
DistinguishedName dn = usernameMapper.buildDn(username);
removeAuthorities(dn, getUserAuthorities(dn, username));
template.unbind(dn);
}
public boolean userExists(String username) {
DistinguishedName dn = usernameMapper.buildDn(username);
try {
Object obj = template.lookup(dn);
if (obj instanceof Context) {
LdapUtils.closeContext((Context) obj);
}
return true;
}
catch (org.springframework.ldap.NameNotFoundException e) {
return false;
}
}
/**
* Creates a DN from a group name.
*
* @param group the name of the group
* @return the DN of the corresponding group, including the groupSearchBase
*/
protected DistinguishedName buildGroupDn(String group) {
DistinguishedName dn = new DistinguishedName(groupSearchBase);
dn.add(groupRoleAttributeName, group.toLowerCase());
return dn;
}
protected void copyToContext(UserDetails user, DirContextAdapter ctx) {
userDetailsMapper.mapUserToContext(user, ctx);
}
protected void addAuthorities(DistinguishedName userDn,
Collection<? extends GrantedAuthority> authorities) {
modifyAuthorities(userDn, authorities, DirContext.ADD_ATTRIBUTE);
}
protected void removeAuthorities(DistinguishedName userDn,
Collection<? extends GrantedAuthority> authorities) {
modifyAuthorities(userDn, authorities, DirContext.REMOVE_ATTRIBUTE);
}
private void modifyAuthorities(final DistinguishedName userDn,
final Collection<? extends GrantedAuthority> authorities, final int modType) {
template.executeReadWrite(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
for (GrantedAuthority authority : authorities) {
String group = convertAuthorityToGroup(authority);
DistinguishedName fullDn = LdapUtils.getFullDn(userDn, ctx);
ModificationItem addGroup = new ModificationItem(modType,
new BasicAttribute(groupMemberAttributeName, fullDn.toUrl()));
ctx.modifyAttributes(buildGroupDn(group),
new ModificationItem[] { addGroup });
}
return null;
}
});
}
private String convertAuthorityToGroup(GrantedAuthority authority) {
String group = authority.getAuthority();
if (group.startsWith(rolePrefix)) {
group = group.substring(rolePrefix.length());
}
return group;
}
public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
this.usernameMapper = usernameMapper;
}
public void setPasswordAttributeName(String passwordAttributeName) {
this.passwordAttributeName = passwordAttributeName;
}
public void setGroupSearchBase(String groupSearchBase) {
this.groupSearchBase = new DistinguishedName(groupSearchBase);
}
public void setGroupRoleAttributeName(String groupRoleAttributeName) {
this.groupRoleAttributeName = groupRoleAttributeName;
}
public void setAttributesToRetrieve(String[] attributesToRetrieve) {
Assert.notNull(attributesToRetrieve);
this.attributesToRetrieve = attributesToRetrieve;
}
public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper) {
this.userDetailsMapper = userDetailsMapper;
}
/**
* Sets the name of the multi-valued attribute which holds the DNs of users who are
* members of a group.
* <p>
* Usually this will be <tt>uniquemember</tt> (the default value) or <tt>member</tt>.
* </p>
*
* @param groupMemberAttributeName the name of the attribute used to store group
* members.
*/
public void setGroupMemberAttributeName(String groupMemberAttributeName) {
Assert.hasText(groupMemberAttributeName);
this.groupMemberAttributeName = groupMemberAttributeName;
this.groupSearchFilter = "(" + groupMemberAttributeName + "={0})";
}
public void setRoleMapper(AttributesMapper roleMapper) {
this.roleMapper = roleMapper;
}
}
@@ -28,158 +28,165 @@ import org.springframework.security.ldap.ppolicy.PasswordPolicyControl;
import org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl;
import org.springframework.util.Assert;
/**
* The context mapper used by the LDAP authentication provider to create an LDAP user object.
* The context mapper used by the LDAP authentication provider to create an LDAP user
* object.
*
* @author Luke Taylor
*/
public class LdapUserDetailsMapper implements UserDetailsContextMapper {
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
private final Log logger = LogFactory.getLog(LdapUserDetailsMapper.class);
private String passwordAttributeName = "userPassword";
private String rolePrefix = "ROLE_";
private String[] roleAttributes = null;
private boolean convertToUpperCase = true;
private final Log logger = LogFactory.getLog(LdapUserDetailsMapper.class);
private String passwordAttributeName = "userPassword";
private String rolePrefix = "ROLE_";
private String[] roleAttributes = null;
private boolean convertToUpperCase = true;
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
String dn = ctx.getNameInNamespace();
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
String dn = ctx.getNameInNamespace();
logger.debug("Mapping user details from context with DN: " + dn);
logger.debug("Mapping user details from context with DN: " + dn);
LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence();
essence.setDn(dn);
LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence();
essence.setDn(dn);
Object passwordValue = ctx.getObjectAttribute(passwordAttributeName);
Object passwordValue = ctx.getObjectAttribute(passwordAttributeName);
if (passwordValue != null) {
essence.setPassword(mapPassword(passwordValue));
}
if (passwordValue != null) {
essence.setPassword(mapPassword(passwordValue));
}
essence.setUsername(username);
essence.setUsername(username);
// Map the roles
for (int i = 0; (roleAttributes != null) && (i < roleAttributes.length); i++) {
String[] rolesForAttribute = ctx.getStringAttributes(roleAttributes[i]);
// Map the roles
for (int i = 0; (roleAttributes != null) && (i < roleAttributes.length); i++) {
String[] rolesForAttribute = ctx.getStringAttributes(roleAttributes[i]);
if (rolesForAttribute == null) {
logger.debug("Couldn't read role attribute '" + roleAttributes[i] + "' for user " + dn);
continue;
}
if (rolesForAttribute == null) {
logger.debug("Couldn't read role attribute '" + roleAttributes[i]
+ "' for user " + dn);
continue;
}
for (String role : rolesForAttribute) {
GrantedAuthority authority = createAuthority(role);
for (String role : rolesForAttribute) {
GrantedAuthority authority = createAuthority(role);
if (authority != null) {
essence.addAuthority(authority);
}
}
}
if (authority != null) {
essence.addAuthority(authority);
}
}
}
// Add the supplied authorities
// Add the supplied authorities
for (GrantedAuthority authority : authorities) {
essence.addAuthority(authority);
}
for (GrantedAuthority authority : authorities) {
essence.addAuthority(authority);
}
// Check for PPolicy data
// Check for PPolicy data
PasswordPolicyResponseControl ppolicy = (PasswordPolicyResponseControl) ctx.getObjectAttribute(PasswordPolicyControl.OID);
PasswordPolicyResponseControl ppolicy = (PasswordPolicyResponseControl) ctx
.getObjectAttribute(PasswordPolicyControl.OID);
if (ppolicy != null) {
essence.setTimeBeforeExpiration(ppolicy.getTimeBeforeExpiration());
essence.setGraceLoginsRemaining(ppolicy.getGraceLoginsRemaining());
}
if (ppolicy != null) {
essence.setTimeBeforeExpiration(ppolicy.getTimeBeforeExpiration());
essence.setGraceLoginsRemaining(ppolicy.getGraceLoginsRemaining());
}
return essence.createUserDetails();
return essence.createUserDetails();
}
}
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
throw new UnsupportedOperationException("LdapUserDetailsMapper only supports reading from a context. Please" +
"use a subclass if mapUserToContext() is required.");
}
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
throw new UnsupportedOperationException(
"LdapUserDetailsMapper only supports reading from a context. Please"
+ "use a subclass if mapUserToContext() is required.");
}
/**
* Extension point to allow customized creation of the user's password from
* the attribute stored in the directory.
*
* @param passwordValue the value of the password attribute
* @return a String representation of the password.
*/
protected String mapPassword(Object passwordValue) {
/**
* Extension point to allow customized creation of the user's password from the
* attribute stored in the directory.
*
* @param passwordValue the value of the password attribute
* @return a String representation of the password.
*/
protected String mapPassword(Object passwordValue) {
if (!(passwordValue instanceof String)) {
// Assume it's binary
passwordValue = new String((byte[]) passwordValue);
}
if (!(passwordValue instanceof String)) {
// Assume it's binary
passwordValue = new String((byte[]) passwordValue);
}
return (String) passwordValue;
return (String) passwordValue;
}
}
/**
* Creates a GrantedAuthority from a role attribute. Override to customize
* authority object creation.
* <p>
* The default implementation converts string attributes to roles, making use of the <tt>rolePrefix</tt>
* and <tt>convertToUpperCase</tt> properties. Non-String attributes are ignored.
* </p>
*
* @param role the attribute returned from
* @return the authority to be added to the list of authorities for the user, or null
* if this attribute should be ignored.
*/
protected GrantedAuthority createAuthority(Object role) {
if (role instanceof String) {
if (convertToUpperCase) {
role = ((String) role).toUpperCase();
}
return new SimpleGrantedAuthority(rolePrefix + role);
}
return null;
}
/**
* Creates a GrantedAuthority from a role attribute. Override to customize authority
* object creation.
* <p>
* The default implementation converts string attributes to roles, making use of the
* <tt>rolePrefix</tt> and <tt>convertToUpperCase</tt> properties. Non-String
* attributes are ignored.
* </p>
*
* @param role the attribute returned from
* @return the authority to be added to the list of authorities for the user, or null
* if this attribute should be ignored.
*/
protected GrantedAuthority createAuthority(Object role) {
if (role instanceof String) {
if (convertToUpperCase) {
role = ((String) role).toUpperCase();
}
return new SimpleGrantedAuthority(rolePrefix + role);
}
return null;
}
/**
* Determines whether role field values will be converted to upper case when loaded.
* The default is true.
*
* @param convertToUpperCase true if the roles should be converted to upper case.
*/
public void setConvertToUpperCase(boolean convertToUpperCase) {
this.convertToUpperCase = convertToUpperCase;
}
/**
* Determines whether role field values will be converted to upper case when loaded.
* The default is true.
*
* @param convertToUpperCase true if the roles should be converted to upper case.
*/
public void setConvertToUpperCase(boolean convertToUpperCase) {
this.convertToUpperCase = convertToUpperCase;
}
/**
* The name of the attribute which contains the user's password.
* Defaults to "userPassword".
*
* @param passwordAttributeName the name of the attribute
*/
public void setPasswordAttributeName(String passwordAttributeName) {
this.passwordAttributeName = passwordAttributeName;
}
/**
* The name of the attribute which contains the user's password. Defaults to
* "userPassword".
*
* @param passwordAttributeName the name of the attribute
*/
public void setPasswordAttributeName(String passwordAttributeName) {
this.passwordAttributeName = passwordAttributeName;
}
/**
* The names of any attributes in the user's entry which represent application
* roles. These will be converted to <tt>GrantedAuthority</tt>s and added to the
* list in the returned LdapUserDetails object. The attribute values must be Strings by default.
*
* @param roleAttributes the names of the role attributes.
*/
public void setRoleAttributes(String[] roleAttributes) {
Assert.notNull(roleAttributes, "roleAttributes array cannot be null");
this.roleAttributes = roleAttributes;
}
/**
* The names of any attributes in the user's entry which represent application roles.
* These will be converted to <tt>GrantedAuthority</tt>s and added to the list in the
* returned LdapUserDetails object. The attribute values must be Strings by default.
*
* @param roleAttributes the names of the role attributes.
*/
public void setRoleAttributes(String[] roleAttributes) {
Assert.notNull(roleAttributes, "roleAttributes array cannot be null");
this.roleAttributes = roleAttributes;
}
/**
* The prefix that should be applied to the role names
* @param rolePrefix the prefix (defaults to "ROLE_").
*/
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
/**
* The prefix that should be applied to the role names
* @param rolePrefix the prefix (defaults to "ROLE_").
*/
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
}
@@ -12,43 +12,48 @@ import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.util.Assert;
/**
* LDAP implementation of UserDetailsService based around an {@link LdapUserSearch}
* and an {@link LdapAuthoritiesPopulator}. The final <tt>UserDetails</tt> object
* returned from <tt>loadUserByUsername</tt> is created by the configured <tt>UserDetailsContextMapper</tt>.
* LDAP implementation of UserDetailsService based around an {@link LdapUserSearch} and an
* {@link LdapAuthoritiesPopulator}. The final <tt>UserDetails</tt> object returned from
* <tt>loadUserByUsername</tt> is created by the configured
* <tt>UserDetailsContextMapper</tt>.
*
* @author Luke Taylor
*/
public class LdapUserDetailsService implements UserDetailsService {
private final LdapUserSearch userSearch;
private final LdapAuthoritiesPopulator authoritiesPopulator;
private UserDetailsContextMapper userDetailsMapper = new LdapUserDetailsMapper();
private final LdapUserSearch userSearch;
private final LdapAuthoritiesPopulator authoritiesPopulator;
private UserDetailsContextMapper userDetailsMapper = new LdapUserDetailsMapper();
public LdapUserDetailsService(LdapUserSearch userSearch) {
this(userSearch, new NullLdapAuthoritiesPopulator());
}
public LdapUserDetailsService(LdapUserSearch userSearch) {
this(userSearch, new NullLdapAuthoritiesPopulator());
}
public LdapUserDetailsService(LdapUserSearch userSearch, LdapAuthoritiesPopulator authoritiesPopulator) {
Assert.notNull(userSearch, "userSearch must not be null");
Assert.notNull(authoritiesPopulator, "authoritiesPopulator must not be null");
this.userSearch = userSearch;
this.authoritiesPopulator = authoritiesPopulator;
}
public LdapUserDetailsService(LdapUserSearch userSearch,
LdapAuthoritiesPopulator authoritiesPopulator) {
Assert.notNull(userSearch, "userSearch must not be null");
Assert.notNull(authoritiesPopulator, "authoritiesPopulator must not be null");
this.userSearch = userSearch;
this.authoritiesPopulator = authoritiesPopulator;
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DirContextOperations userData = userSearch.searchForUser(username);
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
DirContextOperations userData = userSearch.searchForUser(username);
return userDetailsMapper.mapUserFromContext(userData, username,
authoritiesPopulator.getGrantedAuthorities(userData, username));
}
return userDetailsMapper.mapUserFromContext(userData, username,
authoritiesPopulator.getGrantedAuthorities(userData, username));
}
public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper) {
Assert.notNull(userDetailsMapper, "userDetailsMapper must not be null");
this.userDetailsMapper = userDetailsMapper;
}
public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper) {
Assert.notNull(userDetailsMapper, "userDetailsMapper must not be null");
this.userDetailsMapper = userDetailsMapper;
}
private static final class NullLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
public Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations userDetails, String username) {
return AuthorityUtils.NO_AUTHORITIES;
}
}
private static final class NullLdapAuthoritiesPopulator implements
LdapAuthoritiesPopulator {
public Collection<GrantedAuthority> getGrantedAuthorities(
DirContextOperations userDetails, String username) {
return AuthorityUtils.NO_AUTHORITIES;
}
}
}
@@ -25,10 +25,13 @@ import org.springframework.util.StringUtils;
import java.util.*;
/**
* A LDAP authority populator that can recursively search static nested groups. <p>An example of nested groups can be
* A LDAP authority populator that can recursively search static nested groups.
* <p>
* An example of nested groups can be
*
* <pre>
* #Nested groups data
*
*
* dn: uid=javadude,ou=people,dc=springframework,dc=org
* objectclass: top
* objectclass: person
@@ -38,7 +41,7 @@ import java.util.*;
* sn: Dude
* uid: javadude
* userPassword: javadudespassword
*
*
* dn: uid=groovydude,ou=people,dc=springframework,dc=org
* objectclass: top
* objectclass: person
@@ -48,7 +51,7 @@ import java.util.*;
* sn: Dude
* uid: groovydude
* userPassword: groovydudespassword
*
*
* dn: uid=closuredude,ou=people,dc=springframework,dc=org
* objectclass: top
* objectclass: person
@@ -58,7 +61,7 @@ import java.util.*;
* sn: Dude
* uid: closuredude
* userPassword: closuredudespassword
*
*
* dn: uid=scaladude,ou=people,dc=springframework,dc=org
* objectclass: top
* objectclass: person
@@ -68,14 +71,14 @@ import java.util.*;
* sn: Dude
* uid: scaladude
* userPassword: scaladudespassword
*
*
* dn: cn=j-developers,ou=jdeveloper,dc=springframework,dc=org
* objectclass: top
* objectclass: groupOfNames
* cn: j-developers
* ou: jdeveloper
* member: cn=java-developers,ou=groups,dc=springframework,dc=org
*
*
* dn: cn=java-developers,ou=jdeveloper,dc=springframework,dc=org
* objectclass: top
* objectclass: groupOfNames
@@ -84,7 +87,7 @@ import java.util.*;
* member: cn=groovy-developers,ou=groups,dc=springframework,dc=org
* member: cn=scala-developers,ou=groups,dc=springframework,dc=org
* member: uid=javadude,ou=people,dc=springframework,dc=org
*
*
* dn: cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org
* objectclass: top
* objectclass: groupOfNames
@@ -92,20 +95,22 @@ import java.util.*;
* ou: jdeveloper
* member: cn=closure-developers,ou=groups,dc=springframework,dc=org
* member: uid=groovydude,ou=people,dc=springframework,dc=org
*
*
* dn: cn=closure-developers,ou=jdeveloper,dc=springframework,dc=org
* objectclass: top
* objectclass: groupOfNames
* cn: java-developers
* ou: jdeveloper
* member: uid=closuredude,ou=people,dc=springframework,dc=org
*
*
* dn: cn=scala-developers,ou=jdeveloper,dc=springframework,dc=org
* objectclass: top
* objectclass: groupOfNames
* cn: java-developers
* ou: jdeveloper
* member: uid=scaladude,ou=people,dc=springframework,dc=org * </pre>
* member: uid=scaladude,ou=people,dc=springframework,dc=org *
* </pre>
*
* </pre>
* </p>
*
@@ -113,146 +118,159 @@ import java.util.*;
*/
public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopulator {
private static final Log logger = LogFactory.getLog(NestedLdapAuthoritiesPopulator.class);
private static final Log logger = LogFactory
.getLog(NestedLdapAuthoritiesPopulator.class);
/**
* The attribute names to retrieve for each LDAP group
*/
private Set<String> attributeNames;
/**
* The attribute names to retrieve for each LDAP group
*/
private Set<String> attributeNames;
/**
* Maximum search depth - represents the number of recursive searches performed
*/
private int maxSearchDepth = 10;
/**
* Maximum search depth - represents the number of recursive searches performed
*/
private int maxSearchDepth = 10;
/**
* Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be set as a property.
*
* @param contextSource supplies the contexts used to search for user roles.
* @param groupSearchBase if this is an empty string the search will be performed from the root DN of the
*/
public NestedLdapAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
super(contextSource, groupSearchBase);
}
/**
* Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be
* set as a property.
*
* @param contextSource supplies the contexts used to search for user roles.
* @param groupSearchBase if this is an empty string the search will be performed from
* the root DN of the
*/
public NestedLdapAuthoritiesPopulator(ContextSource contextSource,
String groupSearchBase) {
super(contextSource, groupSearchBase);
}
/**
* {@inheritDoc}
*/
@Override
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
if (getGroupSearchBase() == null) {
return new HashSet<GrantedAuthority>();
}
/**
* {@inheritDoc}
*/
@Override
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
if (getGroupSearchBase() == null) {
return new HashSet<GrantedAuthority>();
}
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
performNestedSearch(userDn, username, authorities, getMaxSearchDepth());
performNestedSearch(userDn, username, authorities, getMaxSearchDepth());
return authorities;
}
return authorities;
}
/**
* Performs the nested group search
*
* @param userDn - the userDN to search for, will become the group DN for subsequent searches
* @param username - the username of the user
* @param authorities - the authorities set that will be populated, must not be null
* @param depth - the depth remaining, when 0 recursion will end
*/
private void performNestedSearch(String userDn, String username, Set<GrantedAuthority> authorities, int depth) {
if (depth == 0) {
//back out of recursion
if (logger.isDebugEnabled()) {
logger.debug("Search aborted, max depth reached," +
" for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
+ getGroupSearchFilter() + " in search base '" + getGroupSearchBase() + "'");
}
return;
}
/**
* Performs the nested group search
*
* @param userDn - the userDN to search for, will become the group DN for subsequent
* searches
* @param username - the username of the user
* @param authorities - the authorities set that will be populated, must not be null
* @param depth - the depth remaining, when 0 recursion will end
*/
private void performNestedSearch(String userDn, String username,
Set<GrantedAuthority> authorities, int depth) {
if (depth == 0) {
// back out of recursion
if (logger.isDebugEnabled()) {
logger.debug("Search aborted, max depth reached,"
+ " for roles for user '" + username + "', DN = " + "'" + userDn
+ "', with filter " + getGroupSearchFilter()
+ " in search base '" + getGroupSearchBase() + "'");
}
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
+ getGroupSearchFilter() + " in search base '" + getGroupSearchBase() + "'");
}
if (logger.isDebugEnabled()) {
logger.debug("Searching for roles for user '" + username + "', DN = " + "'"
+ userDn + "', with filter " + getGroupSearchFilter()
+ " in search base '" + getGroupSearchBase() + "'");
}
if (getAttributeNames() == null) {
setAttributeNames(new HashSet<String>());
}
if (StringUtils.hasText(getGroupRoleAttribute()) && !getAttributeNames().contains(getGroupRoleAttribute())) {
getAttributeNames().add(getGroupRoleAttribute());
}
if (getAttributeNames() == null) {
setAttributeNames(new HashSet<String>());
}
if (StringUtils.hasText(getGroupRoleAttribute())
&& !getAttributeNames().contains(getGroupRoleAttribute())) {
getAttributeNames().add(getGroupRoleAttribute());
}
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
getGroupSearchBase(),
getGroupSearchFilter(),
new String[]{userDn, username},
getAttributeNames().toArray(new String[getAttributeNames().size()]));
Set<Map<String, List<String>>> userRoles = getLdapTemplate()
.searchForMultipleAttributeValues(
getGroupSearchBase(),
getGroupSearchFilter(),
new String[] { userDn, username },
getAttributeNames().toArray(
new String[getAttributeNames().size()]));
if (logger.isDebugEnabled()) {
logger.debug("Roles from search: " + userRoles);
}
if (logger.isDebugEnabled()) {
logger.debug("Roles from search: " + userRoles);
}
for (Map<String, List<String>> record : userRoles) {
boolean circular = false;
String dn = record.get(SpringSecurityLdapTemplate.DN_KEY).get(0);
List<String> roleValues = record.get(getGroupRoleAttribute());
Set<String> roles = new HashSet<String>();
if(roleValues != null) {
roles.addAll(roleValues);
}
for (String role : roles) {
if (isConvertToUpperCase()) {
role = role.toUpperCase();
}
role = getRolePrefix() + role;
//if the group already exist, we will not search for it's parents again.
//this prevents a forever loop for a misconfigured ldap directory
circular = circular | (!authorities.add(new LdapAuthority(role, dn, record)));
}
String roleName = roles.size() > 0 ? roles.iterator().next() : dn;
if (!circular) {
performNestedSearch(dn, roleName, authorities, (depth - 1));
}
for (Map<String, List<String>> record : userRoles) {
boolean circular = false;
String dn = record.get(SpringSecurityLdapTemplate.DN_KEY).get(0);
List<String> roleValues = record.get(getGroupRoleAttribute());
Set<String> roles = new HashSet<String>();
if (roleValues != null) {
roles.addAll(roleValues);
}
for (String role : roles) {
if (isConvertToUpperCase()) {
role = role.toUpperCase();
}
role = getRolePrefix() + role;
// if the group already exist, we will not search for it's parents again.
// this prevents a forever loop for a misconfigured ldap directory
circular = circular
| (!authorities.add(new LdapAuthority(role, dn, record)));
}
String roleName = roles.size() > 0 ? roles.iterator().next() : dn;
if (!circular) {
performNestedSearch(dn, roleName, authorities, (depth - 1));
}
}
}
}
}
/**
* Returns the attribute names that this populator has been configured to retrieve Value can be null, represents
* fetch all attributes
*
* @return the attribute names or null for all
*/
private Set<String> getAttributeNames() {
return attributeNames;
}
/**
* Returns the attribute names that this populator has been configured to retrieve
* Value can be null, represents fetch all attributes
*
* @return the attribute names or null for all
*/
private Set<String> getAttributeNames() {
return attributeNames;
}
/**
* Sets the attribute names to retrieve for each ldap groups. Null means retrieve all
*
* @param attributeNames - the names of the LDAP attributes to retrieve
*/
public void setAttributeNames(Set<String> attributeNames) {
this.attributeNames = attributeNames;
}
/**
* Sets the attribute names to retrieve for each ldap groups. Null means retrieve all
*
* @param attributeNames - the names of the LDAP attributes to retrieve
*/
public void setAttributeNames(Set<String> attributeNames) {
this.attributeNames = attributeNames;
}
/**
* How far should a nested search go. Depth is calculated in the number of levels we search up for parent groups.
*
* @return the max search depth, default is 10
*/
private int getMaxSearchDepth() {
return maxSearchDepth;
}
/**
* How far should a nested search go. Depth is calculated in the number of levels we search up for parent groups.
*
* @param maxSearchDepth the max search depth
*/
public void setMaxSearchDepth(int maxSearchDepth) {
this.maxSearchDepth = maxSearchDepth;
}
/**
* How far should a nested search go. Depth is calculated in the number of levels we
* search up for parent groups.
*
* @return the max search depth, default is 10
*/
private int getMaxSearchDepth() {
return maxSearchDepth;
}
/**
* How far should a nested search go. Depth is calculated in the number of levels we
* search up for parent groups.
*
* @param maxSearchDepth the max search depth
*/
public void setMaxSearchDepth(int maxSearchDepth) {
this.maxSearchDepth = maxSearchDepth;
}
}
@@ -14,7 +14,6 @@
*/
package org.springframework.security.ldap.userdetails;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.util.Assert;
@@ -28,128 +27,122 @@ import java.util.ArrayList;
import java.util.Arrays;
/**
* UserDetails implementation whose properties are based on the LDAP schema for <tt>Person</tt>.
* UserDetails implementation whose properties are based on the LDAP schema for
* <tt>Person</tt>.
*
* @author Luke
* @since 2.0
*/
public class Person extends LdapUserDetailsImpl {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private String givenName;
private String sn;
private String description;
private String telephoneNumber;
private List<String> cn = new ArrayList<String>();
private String givenName;
private String sn;
private String description;
private String telephoneNumber;
private List<String> cn = new ArrayList<String>();
protected Person() {
}
protected Person() {
}
public String getGivenName() {
return givenName;
}
public String getGivenName() {
return givenName;
}
public String getSn() {
return sn;
}
public String getSn() {
return sn;
}
public String[] getCn() {
return cn.toArray(new String[cn.size()]);
}
public String[] getCn() {
return cn.toArray(new String[cn.size()]);
}
public String getDescription() {
return description;
}
public String getDescription() {
return description;
}
public String getTelephoneNumber() {
return telephoneNumber;
}
public String getTelephoneNumber() {
return telephoneNumber;
}
protected void populateContext(DirContextAdapter adapter) {
adapter.setAttributeValue("givenName", givenName);
adapter.setAttributeValue("sn", sn);
adapter.setAttributeValues("cn", getCn());
adapter.setAttributeValue("description", getDescription());
adapter.setAttributeValue("telephoneNumber", getTelephoneNumber());
protected void populateContext(DirContextAdapter adapter) {
adapter.setAttributeValue("givenName", givenName);
adapter.setAttributeValue("sn", sn);
adapter.setAttributeValues("cn", getCn());
adapter.setAttributeValue("description", getDescription());
adapter.setAttributeValue("telephoneNumber", getTelephoneNumber());
if(getPassword() != null) {
adapter.setAttributeValue("userPassword", getPassword());
}
adapter.setAttributeValues("objectclass", new String[] {"top", "person"});
}
if (getPassword() != null) {
adapter.setAttributeValue("userPassword", getPassword());
}
adapter.setAttributeValues("objectclass", new String[] { "top", "person" });
}
public static class Essence extends LdapUserDetailsImpl.Essence {
public static class Essence extends LdapUserDetailsImpl.Essence {
public Essence() {
}
public Essence() {
}
public Essence(DirContextOperations ctx) {
super(ctx);
setCn(ctx.getStringAttributes("cn"));
setGivenName(ctx.getStringAttribute("givenName"));
setSn(ctx.getStringAttribute("sn"));
setDescription(ctx.getStringAttribute("description"));
setTelephoneNumber(ctx.getStringAttribute("telephoneNumber"));
Object passo = ctx.getObjectAttribute("userPassword");
public Essence(DirContextOperations ctx) {
super(ctx);
setCn(ctx.getStringAttributes("cn"));
setGivenName(ctx.getStringAttribute("givenName"));
setSn(ctx.getStringAttribute("sn"));
setDescription(ctx.getStringAttribute("description"));
setTelephoneNumber(ctx.getStringAttribute("telephoneNumber"));
Object passo = ctx.getObjectAttribute("userPassword");
if(passo != null) {
String password = LdapUtils.convertPasswordToString(passo);
setPassword(password);
}
}
if (passo != null) {
String password = LdapUtils.convertPasswordToString(passo);
setPassword(password);
}
}
public Essence(Person copyMe) {
super(copyMe);
setGivenName(copyMe.givenName);
setSn(copyMe.sn);
setDescription(copyMe.getDescription());
setTelephoneNumber(copyMe.getTelephoneNumber());
((Person) instance).cn = new ArrayList<String>(copyMe.cn);
}
public Essence(Person copyMe) {
super(copyMe);
setGivenName(copyMe.givenName);
setSn(copyMe.sn);
setDescription(copyMe.getDescription());
setTelephoneNumber(copyMe.getTelephoneNumber());
((Person) instance).cn = new ArrayList<String>(copyMe.cn);
}
protected LdapUserDetailsImpl createTarget() {
return new Person();
}
protected LdapUserDetailsImpl createTarget() {
return new Person();
}
public void setGivenName(String givenName) {
((Person) instance).givenName = givenName;
}
public void setGivenName(String givenName) {
((Person) instance).givenName = givenName;
}
public void setSn(String sn) {
((Person) instance).sn = sn;
}
public void setSn(String sn) {
((Person) instance).sn = sn;
}
public void setCn(String[] cn) {
((Person) instance).cn = Arrays.asList(cn);
}
public void setCn(String[] cn) {
((Person) instance).cn = Arrays.asList(cn);
}
public void addCn(String value) {
((Person) instance).cn.add(value);
}
public void addCn(String value) {
((Person) instance).cn.add(value);
}
public void setTelephoneNumber(String tel) {
((Person) instance).telephoneNumber = tel;
}
public void setTelephoneNumber(String tel) {
((Person) instance).telephoneNumber = tel;
}
public void setDescription(String desc) {
((Person) instance).description = desc;
}
public void setDescription(String desc) {
((Person) instance).description = desc;
}
public LdapUserDetails createUserDetails() {
Person p = (Person) super.createUserDetails();
Assert.hasLength(p.sn);
Assert.notNull(p.cn);
Assert.notEmpty(p.cn);
// TODO: Check contents for null entries
return p;
}
}
public LdapUserDetails createUserDetails() {
Person p = (Person) super.createUserDetails();
Assert.hasLength(p.sn);
Assert.notNull(p.cn);
Assert.notEmpty(p.cn);
// TODO: Check contents for null entries
return p;
}
}
}
@@ -13,20 +13,21 @@ import org.springframework.util.Assert;
*/
public class PersonContextMapper implements UserDetailsContextMapper {
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Person.Essence p = new Person.Essence(ctx);
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
Person.Essence p = new Person.Essence(ctx);
p.setUsername(username);
p.setAuthorities(authorities);
p.setUsername(username);
p.setAuthorities(authorities);
return p.createUserDetails();
return p.createUserDetails();
}
}
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
Assert.isInstanceOf(Person.class, user, "UserDetails must be a Person instance");
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
Assert.isInstanceOf(Person.class, user, "UserDetails must be a Person instance");
Person p = (Person) user;
p.populateContext(ctx);
}
Person p = (Person) user;
p.populateContext(ctx);
}
}
@@ -22,28 +22,31 @@ import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DirContextAdapter;
/**
* Operations to map a UserDetails object to and from a Spring LDAP {@code DirContextOperations} implementation.
* Used by {@code LdapUserDetailsManager} when loading and saving/creating user information, and also by the
* {@code LdapAuthenticationProvider} to allow customization of the user data loaded during authentication.
* Operations to map a UserDetails object to and from a Spring LDAP
* {@code DirContextOperations} implementation. Used by {@code LdapUserDetailsManager}
* when loading and saving/creating user information, and also by the
* {@code LdapAuthenticationProvider} to allow customization of the user data loaded
* during authentication.
*
* @author Luke Taylor
* @since 2.0
*/
public interface UserDetailsContextMapper {
/**
* Creates a fully populated UserDetails object for use by the security framework.
*
* @param ctx the context object which contains the user information.
* @param username the user's supplied login name.
* @param authorities
* @return the user object.
*/
UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities);
/**
* Creates a fully populated UserDetails object for use by the security framework.
*
* @param ctx the context object which contains the user information.
* @param username the user's supplied login name.
* @param authorities
* @return the user object.
*/
UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities);
/**
* Reverse of the above operation. Populates a context object from the supplied user object.
* Called when saving a user, for example.
*/
void mapUserToContext(UserDetails user, DirContextAdapter ctx);
/**
* Reverse of the above operation. Populates a context object from the supplied user
* object. Called when saving a user, for example.
*/
void mapUserToContext(UserDetails user, DirContextAdapter ctx);
}
@@ -23,7 +23,6 @@ import javax.naming.directory.DirContext;
import org.junit.Test;
/**
* Tests {@link LdapUtils}
*
@@ -31,57 +30,68 @@ import org.junit.Test;
*/
public class LdapUtilsTests {
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
@Test
public void testCloseContextSwallowsNamingException() throws Exception {
final DirContext dirCtx = mock(DirContext.class);
doThrow(new NamingException()).when(dirCtx).close();
@Test
public void testCloseContextSwallowsNamingException() throws Exception {
final DirContext dirCtx = mock(DirContext.class);
doThrow(new NamingException()).when(dirCtx).close();
LdapUtils.closeContext(dirCtx);
}
LdapUtils.closeContext(dirCtx);
}
@Test
public void testGetRelativeNameReturnsEmptyStringForDnEqualToBaseName() throws Exception {
final DirContext mockCtx = mock(DirContext.class);
@Test
public void testGetRelativeNameReturnsEmptyStringForDnEqualToBaseName()
throws Exception {
final DirContext mockCtx = mock(DirContext.class);
when(mockCtx.getNameInNamespace()).thenReturn("dc=springframework,dc=org");
when(mockCtx.getNameInNamespace()).thenReturn("dc=springframework,dc=org");
assertEquals("", LdapUtils.getRelativeName("dc=springframework,dc=org", mockCtx));
}
assertEquals("", LdapUtils.getRelativeName("dc=springframework,dc=org", mockCtx));
}
@Test
public void testGetRelativeNameReturnsFullDnWithEmptyBaseName() throws Exception {
final DirContext mockCtx = mock(DirContext.class);
when(mockCtx.getNameInNamespace()).thenReturn("");
@Test
public void testGetRelativeNameReturnsFullDnWithEmptyBaseName() throws Exception {
final DirContext mockCtx = mock(DirContext.class);
when(mockCtx.getNameInNamespace()).thenReturn("");
assertEquals("cn=jane,dc=springframework,dc=org",
LdapUtils.getRelativeName("cn=jane,dc=springframework,dc=org", mockCtx));
}
assertEquals("cn=jane,dc=springframework,dc=org",
LdapUtils.getRelativeName("cn=jane,dc=springframework,dc=org", mockCtx));
}
@Test
public void testGetRelativeNameWorksWithArbitrarySpaces() throws Exception {
final DirContext mockCtx = mock(DirContext.class);
when(mockCtx.getNameInNamespace()).thenReturn("dc=springsecurity,dc = org");
@Test
public void testGetRelativeNameWorksWithArbitrarySpaces() throws Exception {
final DirContext mockCtx = mock(DirContext.class);
when(mockCtx.getNameInNamespace()).thenReturn("dc=springsecurity,dc = org");
assertEquals("cn=jane smith",
LdapUtils.getRelativeName("cn=jane smith, dc = springsecurity , dc=org", mockCtx));
}
assertEquals("cn=jane smith", LdapUtils.getRelativeName(
"cn=jane smith, dc = springsecurity , dc=org", mockCtx));
}
@Test
public void testRootDnsAreParsedFromUrlsCorrectly() {
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine:11389"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine/"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk/"));
assertEquals("dc=springframework,dc=org",
LdapUtils.parseRootDnFromUrl("ldaps://monkeymachine.co.uk/dc=springframework,dc=org"));
assertEquals("dc=springframework,dc=org", LdapUtils.parseRootDnFromUrl("ldap:///dc=springframework,dc=org"));
assertEquals("dc=springframework,dc=org",
LdapUtils.parseRootDnFromUrl("ldap://monkeymachine/dc=springframework,dc=org"));
assertEquals("dc=springframework,dc=org/ou=blah",
LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk/dc=springframework,dc=org/ou=blah"));
assertEquals("dc=springframework,dc=org/ou=blah",
LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk:389/dc=springframework,dc=org/ou=blah"));
}
@Test
public void testRootDnsAreParsedFromUrlsCorrectly() {
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine:11389"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine/"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk/"));
assertEquals(
"dc=springframework,dc=org",
LdapUtils
.parseRootDnFromUrl("ldaps://monkeymachine.co.uk/dc=springframework,dc=org"));
assertEquals("dc=springframework,dc=org",
LdapUtils.parseRootDnFromUrl("ldap:///dc=springframework,dc=org"));
assertEquals(
"dc=springframework,dc=org",
LdapUtils
.parseRootDnFromUrl("ldap://monkeymachine/dc=springframework,dc=org"));
assertEquals(
"dc=springframework,dc=org/ou=blah",
LdapUtils
.parseRootDnFromUrl("ldap://monkeymachine.co.uk/dc=springframework,dc=org/ou=blah"));
assertEquals(
"dc=springframework,dc=org/ou=blah",
LdapUtils
.parseRootDnFromUrl("ldap://monkeymachine.co.uk:389/dc=springframework,dc=org/ou=blah"));
}
}
@@ -18,53 +18,56 @@ import org.junit.Test;
* @author Luke Taylor
*/
public class SpringSecurityAuthenticationSourceTests {
@Before
@After
public void clearContext() {
SecurityContextHolder.clearContext();
}
@Before
@After
public void clearContext() {
SecurityContextHolder.clearContext();
}
@Test
public void principalAndCredentialsAreEmptyWithNoAuthentication() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
assertEquals("", source.getPrincipal());
assertEquals("", source.getCredentials());
}
@Test
public void principalAndCredentialsAreEmptyWithNoAuthentication() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
assertEquals("", source.getPrincipal());
assertEquals("", source.getCredentials());
}
@Test
public void principalIsEmptyForAnonymousUser() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
@Test
public void principalIsEmptyForAnonymousUser() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(
new AnonymousAuthenticationToken("key", "anonUser", AuthorityUtils.createAuthorityList("ignored")));
assertEquals("", source.getPrincipal());
}
SecurityContextHolder.getContext().setAuthentication(
new AnonymousAuthenticationToken("key", "anonUser", AuthorityUtils
.createAuthorityList("ignored")));
assertEquals("", source.getPrincipal());
}
@Test(expected=IllegalArgumentException.class)
public void getPrincipalRejectsNonLdapUserDetailsObject() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(new Object(), "password"));
@Test(expected = IllegalArgumentException.class)
public void getPrincipalRejectsNonLdapUserDetailsObject() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken(new Object(), "password"));
source.getPrincipal();
}
source.getPrincipal();
}
@Test
public void expectedCredentialsAreReturned() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(new Object(), "password"));
@Test
public void expectedCredentialsAreReturned() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken(new Object(), "password"));
assertEquals("password", source.getCredentials());
}
assertEquals("password", source.getCredentials());
}
@Test
public void expectedPrincipalIsReturned() {
LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence();
user.setUsername("joe");
user.setDn(new DistinguishedName("uid=joe,ou=users"));
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken(user.createUserDetails(), null));
@Test
public void expectedPrincipalIsReturned() {
LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence();
user.setUsername("joe");
user.setDn(new DistinguishedName("uid=joe,ou=users"));
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken(user.createUserDetails(), null));
assertEquals("uid=joe,ou=users", source.getPrincipal());
}
assertEquals("uid=joe,ou=users", source.getPrincipal());
}
}
@@ -36,33 +36,36 @@ import org.springframework.ldap.core.DistinguishedName;
@RunWith(MockitoJUnitRunner.class)
public class SpringSecurityLdapTemplateTests {
@Mock
private DirContext ctx;
@Captor
private ArgumentCaptor<SearchControls> searchControls;
@Mock
private NamingEnumeration<SearchResult> resultsEnum;
@Mock
private SearchResult searchResult;
@Mock
private DirContext ctx;
@Captor
private ArgumentCaptor<SearchControls> searchControls;
@Mock
private NamingEnumeration<SearchResult> resultsEnum;
@Mock
private SearchResult searchResult;
// SEC-2405
@Test
public void searchForSingleEntryInternalAllowsReferrals() throws Exception {
String base = "";
String filter = "";
String searchResultName = "ldap://example.com/dc=springframework,dc=org";
Object[] params = new Object[] {};
DirContextAdapter searchResultObject = mock(DirContextAdapter.class);
// SEC-2405
@Test
public void searchForSingleEntryInternalAllowsReferrals() throws Exception {
String base = "";
String filter = "";
String searchResultName = "ldap://example.com/dc=springframework,dc=org";
Object[] params = new Object[] {};
DirContextAdapter searchResultObject = mock(DirContextAdapter.class);
when(ctx.search(any(DistinguishedName.class), eq(filter), eq(params), searchControls.capture())).thenReturn(resultsEnum);
when(resultsEnum.hasMore()).thenReturn(true, false);
when(resultsEnum.next()).thenReturn(searchResult);
when(searchResult.getName()).thenReturn(searchResultName);
when(searchResult.getObject()).thenReturn(searchResultObject);
when(
ctx.search(any(DistinguishedName.class), eq(filter), eq(params),
searchControls.capture())).thenReturn(resultsEnum);
when(resultsEnum.hasMore()).thenReturn(true, false);
when(resultsEnum.next()).thenReturn(searchResult);
when(searchResult.getName()).thenReturn(searchResultName);
when(searchResult.getObject()).thenReturn(searchResultObject);
SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx, mock(SearchControls.class), base, filter, params);
SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx,
mock(SearchControls.class), base, filter, params);
assertThat(searchControls.getValue().getReturningObjFlag()).isTrue();
}
assertThat(searchControls.getValue().getReturningObjFlag()).isTrue();
}
}
@@ -36,7 +36,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
/**
* Tests {@link LdapAuthenticationProvider}.
*
@@ -45,163 +44,193 @@ import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
*/
public class LdapAuthenticationProviderTests {
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
@Test
public void testSupportsUsernamePasswordAuthenticationToken() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
@Test
public void testSupportsUsernamePasswordAuthenticationToken() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(
new MockAuthenticator(), new MockAuthoritiesPopulator());
assertTrue(ldapProvider.supports(UsernamePasswordAuthenticationToken.class));
}
assertTrue(ldapProvider.supports(UsernamePasswordAuthenticationToken.class));
}
@Test
public void testDefaultMapperIsSet() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
@Test
public void testDefaultMapperIsSet() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(
new MockAuthenticator(), new MockAuthoritiesPopulator());
assertTrue(ldapProvider.getUserDetailsContextMapper() instanceof LdapUserDetailsMapper);
}
assertTrue(ldapProvider.getUserDetailsContextMapper() instanceof LdapUserDetailsMapper);
}
@Test
public void testEmptyOrNullUserNameThrowsException() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
@Test
public void testEmptyOrNullUserNameThrowsException() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(
new MockAuthenticator(), new MockAuthoritiesPopulator());
try {
ldapProvider.authenticate(new UsernamePasswordAuthenticationToken(null, "password"));
fail("Expected BadCredentialsException for empty username");
} catch (BadCredentialsException expected) {}
try {
ldapProvider.authenticate(new UsernamePasswordAuthenticationToken(null,
"password"));
fail("Expected BadCredentialsException for empty username");
}
catch (BadCredentialsException expected) {
}
try {
ldapProvider.authenticate(new UsernamePasswordAuthenticationToken("", "bobspassword"));
fail("Expected BadCredentialsException for null username");
} catch (BadCredentialsException expected) {}
}
try {
ldapProvider.authenticate(new UsernamePasswordAuthenticationToken("",
"bobspassword"));
fail("Expected BadCredentialsException for null username");
}
catch (BadCredentialsException expected) {
}
}
@Test(expected=BadCredentialsException.class)
public void usernameNotFoundExceptionIsHiddenByDefault() {
final LdapAuthenticator authenticator = mock(LdapAuthenticator.class);
final UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken("joe", "password");
when(authenticator.authenticate(joe)).thenThrow(new UsernameNotFoundException("nobody"));
@Test(expected = BadCredentialsException.class)
public void usernameNotFoundExceptionIsHiddenByDefault() {
final LdapAuthenticator authenticator = mock(LdapAuthenticator.class);
final UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken(
"joe", "password");
when(authenticator.authenticate(joe)).thenThrow(
new UsernameNotFoundException("nobody"));
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
provider.authenticate(joe);
}
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(
authenticator);
provider.authenticate(joe);
}
@Test(expected=UsernameNotFoundException.class)
public void usernameNotFoundExceptionIsNotHiddenIfConfigured() {
final LdapAuthenticator authenticator = mock(LdapAuthenticator.class);
final UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken("joe", "password");
when(authenticator.authenticate(joe)).thenThrow(new UsernameNotFoundException("nobody"));
@Test(expected = UsernameNotFoundException.class)
public void usernameNotFoundExceptionIsNotHiddenIfConfigured() {
final LdapAuthenticator authenticator = mock(LdapAuthenticator.class);
final UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken(
"joe", "password");
when(authenticator.authenticate(joe)).thenThrow(
new UsernameNotFoundException("nobody"));
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
provider.setHideUserNotFoundExceptions(false);
provider.authenticate(joe);
}
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(
authenticator);
provider.setHideUserNotFoundExceptions(false);
provider.authenticate(joe);
}
@Test
public void normalUsage() {
MockAuthoritiesPopulator populator = new MockAuthoritiesPopulator();
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(), populator);
LdapUserDetailsMapper userMapper = new LdapUserDetailsMapper();
userMapper.setRoleAttributes(new String[] {"ou"});
ldapProvider.setUserDetailsContextMapper(userMapper);
@Test
public void normalUsage() {
MockAuthoritiesPopulator populator = new MockAuthoritiesPopulator();
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(
new MockAuthenticator(), populator);
LdapUserDetailsMapper userMapper = new LdapUserDetailsMapper();
userMapper.setRoleAttributes(new String[] { "ou" });
ldapProvider.setUserDetailsContextMapper(userMapper);
assertNotNull(ldapProvider.getAuthoritiesPopulator());
assertNotNull(ldapProvider.getAuthoritiesPopulator());
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword");
Object authDetails = new Object();
authRequest.setDetails(authDetails);
Authentication authResult = ldapProvider.authenticate(authRequest);
assertEquals("benspassword", authResult.getCredentials());
assertSame(authDetails, authResult.getDetails());
UserDetails user = (UserDetails) authResult.getPrincipal();
assertEquals(2, user.getAuthorities().size());
assertEquals("{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=", user.getPassword());
assertEquals("ben", user.getUsername());
assertEquals("ben", populator.getRequestedUsername());
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
"ben", "benspassword");
Object authDetails = new Object();
authRequest.setDetails(authDetails);
Authentication authResult = ldapProvider.authenticate(authRequest);
assertEquals("benspassword", authResult.getCredentials());
assertSame(authDetails, authResult.getDetails());
UserDetails user = (UserDetails) authResult.getPrincipal();
assertEquals(2, user.getAuthorities().size());
assertEquals("{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=", user.getPassword());
assertEquals("ben", user.getUsername());
assertEquals("ben", populator.getRequestedUsername());
assertTrue(AuthorityUtils.authorityListToSet(user.getAuthorities()).contains("ROLE_FROM_ENTRY"));
assertTrue(AuthorityUtils.authorityListToSet(user.getAuthorities()).contains("ROLE_FROM_POPULATOR"));
}
assertTrue(AuthorityUtils.authorityListToSet(user.getAuthorities()).contains(
"ROLE_FROM_ENTRY"));
assertTrue(AuthorityUtils.authorityListToSet(user.getAuthorities()).contains(
"ROLE_FROM_POPULATOR"));
}
@Test
public void passwordIsSetFromUserDataIfUseAuthenticationRequestCredentialsIsFalse() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
ldapProvider.setUseAuthenticationRequestCredentials(false);
@Test
public void passwordIsSetFromUserDataIfUseAuthenticationRequestCredentialsIsFalse() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(
new MockAuthenticator(), new MockAuthoritiesPopulator());
ldapProvider.setUseAuthenticationRequestCredentials(false);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword");
Authentication authResult = ldapProvider.authenticate(authRequest);
assertEquals("{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=", authResult.getCredentials());
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
"ben", "benspassword");
Authentication authResult = ldapProvider.authenticate(authRequest);
assertEquals("{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=", authResult.getCredentials());
}
}
@Test
public void useWithNullAuthoritiesPopulatorReturnsCorrectRole() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator());
LdapUserDetailsMapper userMapper = new LdapUserDetailsMapper();
userMapper.setRoleAttributes(new String[] {"ou"});
ldapProvider.setUserDetailsContextMapper(userMapper);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword");
UserDetails user = (UserDetails) ldapProvider.authenticate(authRequest).getPrincipal();
assertEquals(1, user.getAuthorities().size());
assertTrue(AuthorityUtils.authorityListToSet(user.getAuthorities()).contains("ROLE_FROM_ENTRY"));
}
@Test
public void useWithNullAuthoritiesPopulatorReturnsCorrectRole() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(
new MockAuthenticator());
LdapUserDetailsMapper userMapper = new LdapUserDetailsMapper();
userMapper.setRoleAttributes(new String[] { "ou" });
ldapProvider.setUserDetailsContextMapper(userMapper);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
"ben", "benspassword");
UserDetails user = (UserDetails) ldapProvider.authenticate(authRequest)
.getPrincipal();
assertEquals(1, user.getAuthorities().size());
assertTrue(AuthorityUtils.authorityListToSet(user.getAuthorities()).contains(
"ROLE_FROM_ENTRY"));
}
@Test
public void authenticateWithNamingException() {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword");
LdapAuthenticator mockAuthenticator = mock(LdapAuthenticator.class);
CommunicationException expectedCause = new CommunicationException(new javax.naming.CommunicationException());
when(mockAuthenticator.authenticate(authRequest)).thenThrow(expectedCause);
@Test
public void authenticateWithNamingException() {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
"ben", "benspassword");
LdapAuthenticator mockAuthenticator = mock(LdapAuthenticator.class);
CommunicationException expectedCause = new CommunicationException(
new javax.naming.CommunicationException());
when(mockAuthenticator.authenticate(authRequest)).thenThrow(expectedCause);
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(mockAuthenticator);
try {
ldapProvider.authenticate(authRequest);
fail("Expected Exception");
} catch(InternalAuthenticationServiceException success) {
assertSame(expectedCause, success.getCause());
}
}
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(
mockAuthenticator);
try {
ldapProvider.authenticate(authRequest);
fail("Expected Exception");
}
catch (InternalAuthenticationServiceException success) {
assertSame(expectedCause, success.getCause());
}
}
//~ Inner Classes ==================================================================================================
// ~ Inner Classes
// ==================================================================================================
class MockAuthenticator implements LdapAuthenticator {
class MockAuthenticator implements LdapAuthenticator {
public DirContextOperations authenticate(Authentication authentication) {
DirContextAdapter ctx = new DirContextAdapter();
ctx.setAttributeValue("ou", "FROM_ENTRY");
String username = authentication.getName();
String password = (String) authentication.getCredentials();
public DirContextOperations authenticate(Authentication authentication) {
DirContextAdapter ctx = new DirContextAdapter();
ctx.setAttributeValue("ou", "FROM_ENTRY");
String username = authentication.getName();
String password = (String) authentication.getCredentials();
if (username.equals("ben") && password.equals("benspassword")) {
ctx.setDn(new DistinguishedName(
"cn=ben,ou=people,dc=springframework,dc=org"));
ctx.setAttributeValue("userPassword", "{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=");
if (username.equals("ben") && password.equals("benspassword")) {
ctx.setDn(new DistinguishedName("cn=ben,ou=people,dc=springframework,dc=org"));
ctx.setAttributeValue("userPassword","{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=");
return ctx;
}
else if (username.equals("jen") && password.equals("")) {
ctx.setDn(new DistinguishedName(
"cn=jen,ou=people,dc=springframework,dc=org"));
return ctx;
} else if (username.equals("jen") && password.equals("")) {
ctx.setDn(new DistinguishedName("cn=jen,ou=people,dc=springframework,dc=org"));
return ctx;
}
return ctx;
}
throw new BadCredentialsException("Authentication failed.");
}
}
throw new BadCredentialsException("Authentication failed.");
}
}
class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator {
String username;
class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator {
String username;
public Collection<GrantedAuthority> getGrantedAuthorities(
DirContextOperations userCtx, String username) {
this.username = username;
return AuthorityUtils.createAuthorityList("ROLE_FROM_POPULATOR");
}
public Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations userCtx, String username) {
this.username = username;
return AuthorityUtils.createAuthorityList("ROLE_FROM_POPULATOR");
}
String getRequestedUsername() {
return username;
}
}
String getRequestedUsername() {
return username;
}
}
}
@@ -21,95 +21,113 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.security.authentication.encoding.LdapShaPasswordEncoder;
/**
* Tests {@link LdapShaPasswordEncoder}.
*
* @author Luke Taylor
*/
public class LdapShaPasswordEncoderTests {
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
LdapShaPasswordEncoder sha;
LdapShaPasswordEncoder sha;
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
@Before
public void setUp() throws Exception {
sha = new LdapShaPasswordEncoder();
}
@Before
public void setUp() throws Exception {
sha = new LdapShaPasswordEncoder();
}
@Test
public void invalidPasswordFails() {
assertFalse(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "wrongpassword", null));
}
@Test
public void invalidPasswordFails() {
assertFalse(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=",
"wrongpassword", null));
}
@Test
public void invalidSaltedPasswordFails() {
assertFalse(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "wrongpassword", null));
assertFalse(sha.isPasswordValid("{SSHA}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "wrongpassword", null));
}
@Test
public void invalidSaltedPasswordFails() {
assertFalse(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX",
"wrongpassword", null));
assertFalse(sha.isPasswordValid("{SSHA}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd",
"wrongpassword", null));
}
@Test(expected=IllegalArgumentException.class)
public void nonByteArraySaltThrowsException() {
sha.encodePassword("password", "AStringNotAByteArray");
}
@Test(expected = IllegalArgumentException.class)
public void nonByteArraySaltThrowsException() {
sha.encodePassword("password", "AStringNotAByteArray");
}
/**
* Test values generated by 'slappasswd -h {SHA} -s boabspasswurd'
*/
@Test
public void validPasswordSucceeds() {
sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
sha.setForceLowerCasePrefix(true);
assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
}
/**
* Test values generated by 'slappasswd -h {SHA} -s boabspasswurd'
*/
@Test
public void validPasswordSucceeds() {
sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=",
"boabspasswurd", null));
assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=",
"boabspasswurd", null));
sha.setForceLowerCasePrefix(true);
assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=",
"boabspasswurd", null));
assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=",
"boabspasswurd", null));
}
/**
* Test values generated by 'slappasswd -s boabspasswurd'
*/
@Test
public void validSaltedPasswordSucceeds() {
sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null));
sha.setForceLowerCasePrefix(true);
assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null));
}
/**
* Test values generated by 'slappasswd -s boabspasswurd'
*/
@Test
public void validSaltedPasswordSucceeds() {
sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX",
"boabspasswurd", null));
assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd",
"boabspasswurd", null));
sha.setForceLowerCasePrefix(true);
assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX",
"boabspasswurd", null));
assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd",
"boabspasswurd", null));
}
@Test
// SEC-1031
public void fullLengthOfHashIsUsedInComparison() throws Exception {
// Change the first hash character from '2' to '3'
assertFalse(sha.isPasswordValid("{SSHA}35ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null));
// Change the last hash character from 'X' to 'Y'
assertFalse(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgY", "boabspasswurd", null));
}
@Test
// SEC-1031
public void fullLengthOfHashIsUsedInComparison() throws Exception {
// Change the first hash character from '2' to '3'
assertFalse(sha.isPasswordValid("{SSHA}35ro4PKC8jhQZ26jVsozhX/xaP0suHgX",
"boabspasswurd", null));
// Change the last hash character from 'X' to 'Y'
assertFalse(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgY",
"boabspasswurd", null));
}
@Test
public void correctPrefixCaseIsUsed() {
sha.setForceLowerCasePrefix(false);
assertEquals("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null));
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{SSHA}"));
@Test
public void correctPrefixCaseIsUsed() {
sha.setForceLowerCasePrefix(false);
assertEquals("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=",
sha.encodePassword("boabspasswurd", null));
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith(
"{SSHA}"));
sha.setForceLowerCasePrefix(true);
assertEquals("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null));
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{ssha}"));
sha.setForceLowerCasePrefix(true);
assertEquals("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=",
sha.encodePassword("boabspasswurd", null));
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith(
"{ssha}"));
}
}
@Test(expected=IllegalArgumentException.class)
public void invalidPrefixIsRejected() {
sha.isPasswordValid("{MD9}xxxxxxxxxx" , "somepassword", null);
}
@Test(expected = IllegalArgumentException.class)
public void invalidPrefixIsRejected() {
sha.isPasswordValid("{MD9}xxxxxxxxxx", "somepassword", null);
}
@Test(expected=IllegalArgumentException.class)
public void malformedPrefixIsRejected() {
// No right brace
sha.isPasswordValid("{SSHA25ro4PKC8jhQZ26jVsozhX/xaP0suHgX" , "somepassword", null);
}
@Test(expected = IllegalArgumentException.class)
public void malformedPrefixIsRejected() {
// No right brace
sha.isPasswordValid("{SSHA25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "somepassword", null);
}
}
@@ -19,29 +19,31 @@ import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.ldap.core.DirContextOperations;
/**
*
*
* @author Luke Taylor
*/
public class MockUserSearch implements LdapUserSearch {
//~ Instance fields ================================================================================================
// ~ Instance fields
// ================================================================================================
DirContextOperations user;
DirContextOperations user;
//~ Constructors ===================================================================================================
// ~ Constructors
// ===================================================================================================
public MockUserSearch() {
}
public MockUserSearch() {
}
public MockUserSearch(DirContextOperations user) {
this.user = user;
}
public MockUserSearch(DirContextOperations user) {
this.user = user;
}
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
public DirContextOperations searchForUser(String username) {
return user;
}
public DirContextOperations searchForUser(String username) {
return user;
}
}
@@ -29,39 +29,42 @@ import org.junit.Test;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
/**
*
* @author Luke Taylor
*/
public class PasswordComparisonAuthenticatorMockTests {
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
@Test
public void ldapCompareOperationIsUsedWhenPasswordIsNotRetrieved() throws Exception {
final DirContext dirCtx = mock(DirContext.class);
final BaseLdapPathContextSource source = mock(BaseLdapPathContextSource.class);
final BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("uid", "bob"));
@Test
public void ldapCompareOperationIsUsedWhenPasswordIsNotRetrieved() throws Exception {
final DirContext dirCtx = mock(DirContext.class);
final BaseLdapPathContextSource source = mock(BaseLdapPathContextSource.class);
final BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("uid", "bob"));
PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(source);
PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(
source);
authenticator.setUserDnPatterns(new String[] {"cn={0},ou=people"});
authenticator.setUserDnPatterns(new String[] { "cn={0},ou=people" });
// Get the mock to return an empty attribute set
when(source.getReadOnlyContext()).thenReturn(dirCtx);
when(dirCtx.getAttributes(eq("cn=Bob,ou=people"), any(String[].class))).thenReturn(attrs);
when(dirCtx.getNameInNamespace()).thenReturn("dc=springframework,dc=org");
// Get the mock to return an empty attribute set
when(source.getReadOnlyContext()).thenReturn(dirCtx);
when(dirCtx.getAttributes(eq("cn=Bob,ou=people"), any(String[].class)))
.thenReturn(attrs);
when(dirCtx.getNameInNamespace()).thenReturn("dc=springframework,dc=org");
// Setup a single return value (i.e. success)
final NamingEnumeration searchResults = new BasicAttributes("", null).getAll();
// Setup a single return value (i.e. success)
final NamingEnumeration searchResults = new BasicAttributes("", null).getAll();
when(dirCtx.search(eq("cn=Bob,ou=people"),
eq("(userPassword={0})"),
any(Object[].class),
any(SearchControls.class))).thenReturn(searchResults);
when(
dirCtx.search(eq("cn=Bob,ou=people"), eq("(userPassword={0})"),
any(Object[].class), any(SearchControls.class))).thenReturn(
searchResults);
authenticator.authenticate(new UsernamePasswordAuthenticationToken("Bob","bobspassword"));
}
authenticator.authenticate(new UsernamePasswordAuthenticationToken("Bob",
"bobspassword"));
}
}
@@ -57,367 +57,413 @@ import static org.springframework.security.ldap.authentication.ad.ActiveDirector
* @author Rob Winch
*/
public class ActiveDirectoryLdapAuthenticationProviderTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
ActiveDirectoryLdapAuthenticationProvider provider;
UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken("joe", "password");
@Before
public void setUp() throws Exception {
provider = new ActiveDirectoryLdapAuthenticationProvider("mydomain.eu", "ldap://192.168.1.200/");
}
@Test
public void bindPrincipalIsCreatedCorrectly() throws Exception {
assertEquals("joe@mydomain.eu", provider.createBindPrincipal("joe"));
assertEquals("joe@mydomain.eu", provider.createBindPrincipal("joe@mydomain.eu"));
}
@Test
public void successfulAuthenticationProducesExpectedAuthorities() throws Exception {
checkAuthentication("dc=mydomain,dc=eu", provider);
}
// SEC-1915
@Test
public void customSearchFilterIsUsedForSuccessfulAuthentication() throws Exception {
//given
String customSearchFilter = "(&(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", dca, dca.getAttributes());
when(ctx.search(any(Name.class), eq(customSearchFilter), any(Object[].class), any(SearchControls.class)))
.thenReturn(new MockNamingEnumeration(sr));
ActiveDirectoryLdapAuthenticationProvider customProvider
= new ActiveDirectoryLdapAuthenticationProvider("mydomain.eu", "ldap://192.168.1.200/");
customProvider.contextFactory = createContextFactoryReturning(ctx);
//when
customProvider.setSearchFilter(customSearchFilter);
Authentication result = customProvider.authenticate(joe);
//then
assertTrue(result.isAuthenticated());
}
@Test
public void defaultSearchFilter() throws Exception {
//given
final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
when(ctx.search(any(Name.class), eq(defaultSearchFilter), any(Object[].class), any(SearchControls.class)))
.thenReturn(new MockNamingEnumeration(sr));
ActiveDirectoryLdapAuthenticationProvider customProvider
= new ActiveDirectoryLdapAuthenticationProvider("mydomain.eu", "ldap://192.168.1.200/");
customProvider.contextFactory = createContextFactoryReturning(ctx);
//when
Authentication result = customProvider.authenticate(joe);
//then
assertTrue(result.isAuthenticated());
verify(ctx).search(any(DistinguishedName.class), eq(defaultSearchFilter), any(Object[].class), any(SearchControls.class));
}
// SEC-2897
@Test
public void bindPrincipalUsed() throws Exception {
//given
final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
ArgumentCaptor<Object[]> captor = ArgumentCaptor.forClass(Object[].class);
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
when(ctx.search(any(Name.class), eq(defaultSearchFilter), captor.capture(), any(SearchControls.class)))
.thenReturn(new MockNamingEnumeration(sr));
ActiveDirectoryLdapAuthenticationProvider customProvider
= new ActiveDirectoryLdapAuthenticationProvider("mydomain.eu", "ldap://192.168.1.200/");
customProvider.contextFactory = createContextFactoryReturning(ctx);
//when
Authentication result = customProvider.authenticate(joe);
//then
assertThat(captor.getValue()).containsOnly("joe@mydomain.eu");
assertTrue(result.isAuthenticated());
}
@Test(expected = IllegalArgumentException.class)
public void setSearchFilterNull() {
provider.setSearchFilter(null);
}
@Test(expected = IllegalArgumentException.class)
public void setSearchFilterEmpty() {
provider.setSearchFilter(" ");
}
@Test
public void nullDomainIsSupportedIfAuthenticatingWithFullUserPrincipal() throws Exception {
provider = new ActiveDirectoryLdapAuthenticationProvider(null, "ldap://192.168.1.200/");
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
when(ctx.search(eq(new DistinguishedName("DC=mydomain,DC=eu")), any(String.class), any(Object[].class), any(SearchControls.class)))
.thenReturn(new MockNamingEnumeration(sr));
provider.contextFactory = createContextFactoryReturning(ctx);
try {
provider.authenticate(joe);
fail("Expected BadCredentialsException for user with no domain information");
} catch (BadCredentialsException expected) {
}
provider.authenticate(new UsernamePasswordAuthenticationToken("joe@mydomain.eu", "password"));
}
@Test(expected = BadCredentialsException.class)
public void failedUserSearchCausesBadCredentials() 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)))
.thenThrow(new NameNotFoundException());
provider.contextFactory = createContextFactoryReturning(ctx);
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<SearchResult>());
provider.contextFactory = createContextFactoryReturning(ctx);
provider.authenticate(joe);
}
// SEC-2500
@Test(expected = BadCredentialsException.class)
public void sec2500PreventAnonymousBind() {
provider.authenticate(new UsernamePasswordAuthenticationToken("rwinch", ""));
}
@SuppressWarnings("unchecked")
@Test(expected = IncorrectResultSizeDataAccessException.class)
public void duplicateUserSearchCausesError() throws Exception {
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
NamingEnumeration<SearchResult> searchResults = mock(NamingEnumeration.class);
when(searchResults.hasMore()).thenReturn(true,true,false);
SearchResult searchResult = mock(SearchResult.class);
when(searchResult.getObject()).thenReturn(new DirContextAdapter("ou=1"),new DirContextAdapter("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)
public void userNotFoundIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg + "525, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = BadCredentialsException.class)
public void incorrectPasswordIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg + "52e, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = BadCredentialsException.class)
public void notPermittedIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg + "530, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test
public void passwordNeedsResetIsCorrectlyMapped() {
final String dataCode = "773";
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg + dataCode+", xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
thrown.expect(BadCredentialsException.class);
thrown.expect(new BaseMatcher<BadCredentialsException>() {
private Matcher<Object> causeInstance = CoreMatchers.instanceOf(ActiveDirectoryAuthenticationException.class);
private Matcher<String> causeDataCode = CoreMatchers.equalTo(dataCode);
public boolean matches(Object that) {
Throwable t = (Throwable) that;
ActiveDirectoryAuthenticationException cause = (ActiveDirectoryAuthenticationException) t.getCause();
return causeInstance.matches(cause) && causeDataCode.matches(cause.getDataCode());
}
public void describeTo(Description desc) {
desc.appendText("getCause() ");
causeInstance.describeTo(desc);
desc.appendText("getCause().getDataCode() ");
causeDataCode.describeTo(desc);
}
});
provider.authenticate(joe);
}
@Test(expected = CredentialsExpiredException.class)
public void expiredPasswordIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg + "532, xxxx]"));
try {
provider.authenticate(joe);
fail();
} catch (BadCredentialsException expected) {
}
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = DisabledException.class)
public void accountDisabledIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg + "533, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = AccountExpiredException.class)
public void accountExpiredIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg + "701, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = LockedException.class)
public void accountLockedIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg + "775, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = BadCredentialsException.class)
public void unknownErrorCodeIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg + "999, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = BadCredentialsException.class)
public void errorWithNoSubcodeIsHandledCleanly() throws Exception {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(msg));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = org.springframework.ldap.CommunicationException.class)
public void nonAuthenticationExceptionIsConvertedToSpringLdapException() throws Exception {
provider.contextFactory = createContextFactoryThrowing(new CommunicationException(msg));
provider.authenticate(joe);
}
@Test
public void rootDnProvidedSeparatelyFromDomainAlsoWorks() throws Exception {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("mydomain.eu", "ldap://192.168.1.200/", "dc=ad,dc=eu,dc=mydomain");
checkAuthentication("dc=ad,dc=eu,dc=mydomain", provider);
}
ContextFactory createContextFactoryThrowing(final NamingException e) {
return new ContextFactory() {
@Override
DirContext createContext(Hashtable<?, ?> env) throws NamingException {
throw e;
}
};
}
ContextFactory createContextFactoryReturning(final DirContext ctx) {
return new ContextFactory() {
@Override
DirContext createContext(Hashtable<?, ?> env) throws NamingException {
return ctx;
}
};
}
private void checkAuthentication(String rootDn, ActiveDirectoryLdapAuthenticationProvider provider) throws NamingException {
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
@SuppressWarnings("deprecation") DistinguishedName searchBaseDn = new DistinguishedName(rootDn);
when(ctx.search(eq(searchBaseDn), any(String.class), any(Object[].class), any(SearchControls.class)))
.thenReturn(new MockNamingEnumeration(sr))
.thenReturn(new MockNamingEnumeration(sr));
provider.contextFactory = createContextFactoryReturning(ctx);
Authentication result = provider.authenticate(joe);
assertEquals(0, result.getAuthorities().size());
dca.addAttributeValue("memberOf","CN=Admin,CN=Users,DC=mydomain,DC=eu");
result = provider.authenticate(joe);
assertEquals(1, result.getAuthorities().size());
}
static class MockNamingEnumeration implements NamingEnumeration<SearchResult> {
private SearchResult sr;
public MockNamingEnumeration(SearchResult sr) {
this.sr = sr;
}
public SearchResult next() {
SearchResult result = sr;
sr = null;
return result;
}
public boolean hasMore() {
return sr != null;
}
public void close() {
}
public boolean hasMoreElements() {
return hasMore();
}
public SearchResult nextElement() {
return next();
}
}
@Rule
public ExpectedException thrown = ExpectedException.none();
ActiveDirectoryLdapAuthenticationProvider provider;
UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken(
"joe", "password");
@Before
public void setUp() throws Exception {
provider = new ActiveDirectoryLdapAuthenticationProvider("mydomain.eu",
"ldap://192.168.1.200/");
}
@Test
public void bindPrincipalIsCreatedCorrectly() throws Exception {
assertEquals("joe@mydomain.eu", provider.createBindPrincipal("joe"));
assertEquals("joe@mydomain.eu", provider.createBindPrincipal("joe@mydomain.eu"));
}
@Test
public void successfulAuthenticationProducesExpectedAuthorities() throws Exception {
checkAuthentication("dc=mydomain,dc=eu", provider);
}
// SEC-1915
@Test
public void customSearchFilterIsUsedForSuccessfulAuthentication() throws Exception {
// given
String customSearchFilter = "(&(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", dca,
dca.getAttributes());
when(
ctx.search(any(Name.class), eq(customSearchFilter), any(Object[].class),
any(SearchControls.class))).thenReturn(
new MockNamingEnumeration(sr));
ActiveDirectoryLdapAuthenticationProvider customProvider = new ActiveDirectoryLdapAuthenticationProvider(
"mydomain.eu", "ldap://192.168.1.200/");
customProvider.contextFactory = createContextFactoryReturning(ctx);
// when
customProvider.setSearchFilter(customSearchFilter);
Authentication result = customProvider.authenticate(joe);
// then
assertTrue(result.isAuthenticated());
}
@Test
public void defaultSearchFilter() throws Exception {
// given
final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca,
dca.getAttributes());
when(
ctx.search(any(Name.class), eq(defaultSearchFilter), any(Object[].class),
any(SearchControls.class))).thenReturn(
new MockNamingEnumeration(sr));
ActiveDirectoryLdapAuthenticationProvider customProvider = new ActiveDirectoryLdapAuthenticationProvider(
"mydomain.eu", "ldap://192.168.1.200/");
customProvider.contextFactory = createContextFactoryReturning(ctx);
// when
Authentication result = customProvider.authenticate(joe);
// then
assertTrue(result.isAuthenticated());
verify(ctx).search(any(DistinguishedName.class), eq(defaultSearchFilter),
any(Object[].class), any(SearchControls.class));
}
// SEC-2897
@Test
public void bindPrincipalUsed() throws Exception {
// given
final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
ArgumentCaptor<Object[]> captor = ArgumentCaptor.forClass(Object[].class);
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca,
dca.getAttributes());
when(
ctx.search(any(Name.class), eq(defaultSearchFilter), captor.capture(),
any(SearchControls.class))).thenReturn(
new MockNamingEnumeration(sr));
ActiveDirectoryLdapAuthenticationProvider customProvider = new ActiveDirectoryLdapAuthenticationProvider(
"mydomain.eu", "ldap://192.168.1.200/");
customProvider.contextFactory = createContextFactoryReturning(ctx);
// when
Authentication result = customProvider.authenticate(joe);
// then
assertThat(captor.getValue()).containsOnly("joe@mydomain.eu");
assertTrue(result.isAuthenticated());
}
@Test(expected = IllegalArgumentException.class)
public void setSearchFilterNull() {
provider.setSearchFilter(null);
}
@Test(expected = IllegalArgumentException.class)
public void setSearchFilterEmpty() {
provider.setSearchFilter(" ");
}
@Test
public void nullDomainIsSupportedIfAuthenticatingWithFullUserPrincipal()
throws Exception {
provider = new ActiveDirectoryLdapAuthenticationProvider(null,
"ldap://192.168.1.200/");
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca,
dca.getAttributes());
when(
ctx.search(eq(new DistinguishedName("DC=mydomain,DC=eu")),
any(String.class), any(Object[].class), any(SearchControls.class)))
.thenReturn(new MockNamingEnumeration(sr));
provider.contextFactory = createContextFactoryReturning(ctx);
try {
provider.authenticate(joe);
fail("Expected BadCredentialsException for user with no domain information");
}
catch (BadCredentialsException expected) {
}
provider.authenticate(new UsernamePasswordAuthenticationToken("joe@mydomain.eu",
"password"));
}
@Test(expected = BadCredentialsException.class)
public void failedUserSearchCausesBadCredentials() 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)))
.thenThrow(new NameNotFoundException());
provider.contextFactory = createContextFactoryReturning(ctx);
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<SearchResult>());
provider.contextFactory = createContextFactoryReturning(ctx);
provider.authenticate(joe);
}
// SEC-2500
@Test(expected = BadCredentialsException.class)
public void sec2500PreventAnonymousBind() {
provider.authenticate(new UsernamePasswordAuthenticationToken("rwinch", ""));
}
@SuppressWarnings("unchecked")
@Test(expected = IncorrectResultSizeDataAccessException.class)
public void duplicateUserSearchCausesError() throws Exception {
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
NamingEnumeration<SearchResult> searchResults = mock(NamingEnumeration.class);
when(searchResults.hasMore()).thenReturn(true, true, false);
SearchResult searchResult = mock(SearchResult.class);
when(searchResult.getObject()).thenReturn(new DirContextAdapter("ou=1"),
new DirContextAdapter("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)
public void userNotFoundIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg + "525, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = BadCredentialsException.class)
public void incorrectPasswordIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg + "52e, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = BadCredentialsException.class)
public void notPermittedIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg + "530, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test
public void passwordNeedsResetIsCorrectlyMapped() {
final String dataCode = "773";
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg + dataCode + ", xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
thrown.expect(BadCredentialsException.class);
thrown.expect(new BaseMatcher<BadCredentialsException>() {
private Matcher<Object> causeInstance = CoreMatchers
.instanceOf(ActiveDirectoryAuthenticationException.class);
private Matcher<String> causeDataCode = CoreMatchers.equalTo(dataCode);
public boolean matches(Object that) {
Throwable t = (Throwable) that;
ActiveDirectoryAuthenticationException cause = (ActiveDirectoryAuthenticationException) t
.getCause();
return causeInstance.matches(cause)
&& causeDataCode.matches(cause.getDataCode());
}
public void describeTo(Description desc) {
desc.appendText("getCause() ");
causeInstance.describeTo(desc);
desc.appendText("getCause().getDataCode() ");
causeDataCode.describeTo(desc);
}
});
provider.authenticate(joe);
}
@Test(expected = CredentialsExpiredException.class)
public void expiredPasswordIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg + "532, xxxx]"));
try {
provider.authenticate(joe);
fail();
}
catch (BadCredentialsException expected) {
}
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = DisabledException.class)
public void accountDisabledIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg + "533, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = AccountExpiredException.class)
public void accountExpiredIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg + "701, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = LockedException.class)
public void accountLockedIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg + "775, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = BadCredentialsException.class)
public void unknownErrorCodeIsCorrectlyMapped() {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg + "999, xxxx]"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = BadCredentialsException.class)
public void errorWithNoSubcodeIsHandledCleanly() throws Exception {
provider.contextFactory = createContextFactoryThrowing(new AuthenticationException(
msg));
provider.setConvertSubErrorCodesToExceptions(true);
provider.authenticate(joe);
}
@Test(expected = org.springframework.ldap.CommunicationException.class)
public void nonAuthenticationExceptionIsConvertedToSpringLdapException()
throws Exception {
provider.contextFactory = createContextFactoryThrowing(new CommunicationException(
msg));
provider.authenticate(joe);
}
@Test
public void rootDnProvidedSeparatelyFromDomainAlsoWorks() throws Exception {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
"mydomain.eu", "ldap://192.168.1.200/", "dc=ad,dc=eu,dc=mydomain");
checkAuthentication("dc=ad,dc=eu,dc=mydomain", provider);
}
ContextFactory createContextFactoryThrowing(final NamingException e) {
return new ContextFactory() {
@Override
DirContext createContext(Hashtable<?, ?> env) throws NamingException {
throw e;
}
};
}
ContextFactory createContextFactoryReturning(final DirContext ctx) {
return new ContextFactory() {
@Override
DirContext createContext(Hashtable<?, ?> env) throws NamingException {
return ctx;
}
};
}
private void checkAuthentication(String rootDn,
ActiveDirectoryLdapAuthenticationProvider provider) throws NamingException {
DirContext ctx = mock(DirContext.class);
when(ctx.getNameInNamespace()).thenReturn("");
DirContextAdapter dca = new DirContextAdapter();
SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca,
dca.getAttributes());
@SuppressWarnings("deprecation")
DistinguishedName searchBaseDn = new DistinguishedName(rootDn);
when(
ctx.search(eq(searchBaseDn), any(String.class), any(Object[].class),
any(SearchControls.class))).thenReturn(
new MockNamingEnumeration(sr)).thenReturn(new MockNamingEnumeration(sr));
provider.contextFactory = createContextFactoryReturning(ctx);
Authentication result = provider.authenticate(joe);
assertEquals(0, result.getAuthorities().size());
dca.addAttributeValue("memberOf", "CN=Admin,CN=Users,DC=mydomain,DC=eu");
result = provider.authenticate(joe);
assertEquals(1, result.getAuthorities().size());
}
static class MockNamingEnumeration implements NamingEnumeration<SearchResult> {
private SearchResult sr;
public MockNamingEnumeration(SearchResult sr) {
this.sr = sr;
}
public SearchResult next() {
SearchResult result = sr;
sr = null;
return result;
}
public boolean hasMore() {
return sr != null;
}
public void close() {
}
public boolean hasMoreElements() {
return hasMore();
}
public SearchResult nextElement() {
return next();
}
}
}
@@ -15,52 +15,44 @@ import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl;
/**
* Test cases which run against an OpenLDAP server.
* <p>
* Run the script in the module root to start the server and import the data before running.
* Run the script in the module root to start the server and import the data before
* running.
* @author Luke Taylor
* @since 3.0
*/
public class OpenLDAPIntegrationTestSuite {
PasswordPolicyAwareContextSource cs;
/*
@Before
public void createContextSource() throws Exception {
cs = new PasswordPolicyAwareContextSource("ldap://localhost:22389/dc=springsource,dc=com");
cs.setUserDn("cn=admin,dc=springsource,dc=com");
cs.setPassword("password");
cs.afterPropertiesSet();
}
@Test
public void simpleBindSucceeds() throws Exception {
BindAuthenticator authenticator = new BindAuthenticator(cs);
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=users"});
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
provider.authenticate(new UsernamePasswordAuthenticationToken("luke","password"));
}
@Test(expected=LockedException.class)
public void repeatedBindWithWrongPasswordLocksAccount() throws Exception {
BindAuthenticator authenticator = new BindAuthenticator(cs);
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=users"});
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
for (int count=1; count < 4; count++) {
try {
Authentication a = provider.authenticate(new UsernamePasswordAuthenticationToken("lockme","wrong"));
LdapUserDetailsImpl ud = (LdapUserDetailsImpl) a.getPrincipal();
assertTrue(ud.getTimeBeforeExpiration() < Integer.MAX_VALUE && ud.getTimeBeforeExpiration() > 0);
} catch (BadCredentialsException expected) {
}
}
}
@Test
public void passwordExpiryTimeIsDetectedCorrectly() throws Exception {
BindAuthenticator authenticator = new BindAuthenticator(cs);
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=users"});
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
Authentication a = provider.authenticate(new UsernamePasswordAuthenticationToken("expireme","password"));
PasswordPolicyData ud = (LdapUserDetailsImpl) a.getPrincipal();
assertTrue(ud.getTimeBeforeExpiration() < Integer.MAX_VALUE && ud.getTimeBeforeExpiration() > 0);
}
*/
PasswordPolicyAwareContextSource cs;
/*
* @Before public void createContextSource() throws Exception { cs = new
* PasswordPolicyAwareContextSource("ldap://localhost:22389/dc=springsource,dc=com");
* cs.setUserDn("cn=admin,dc=springsource,dc=com"); cs.setPassword("password");
* cs.afterPropertiesSet(); }
*
* @Test public void simpleBindSucceeds() throws Exception { BindAuthenticator
* authenticator = new BindAuthenticator(cs); authenticator.setUserDnPatterns(new
* String[] {"uid={0},ou=users"}); LdapAuthenticationProvider provider = new
* LdapAuthenticationProvider(authenticator); provider.authenticate(new
* UsernamePasswordAuthenticationToken("luke","password")); }
*
* @Test(expected=LockedException.class) public void
* repeatedBindWithWrongPasswordLocksAccount() throws Exception { BindAuthenticator
* authenticator = new BindAuthenticator(cs); authenticator.setUserDnPatterns(new
* String[] {"uid={0},ou=users"}); LdapAuthenticationProvider provider = new
* LdapAuthenticationProvider(authenticator); for (int count=1; count < 4; count++) {
* try { Authentication a = provider.authenticate(new
* UsernamePasswordAuthenticationToken("lockme","wrong")); LdapUserDetailsImpl ud =
* (LdapUserDetailsImpl) a.getPrincipal(); assertTrue(ud.getTimeBeforeExpiration() <
* Integer.MAX_VALUE && ud.getTimeBeforeExpiration() > 0); } catch
* (BadCredentialsException expected) { } } }
*
* @Test public void passwordExpiryTimeIsDetectedCorrectly() throws Exception {
* BindAuthenticator authenticator = new BindAuthenticator(cs);
* authenticator.setUserDnPatterns(new String[] {"uid={0},ou=users"});
* LdapAuthenticationProvider provider = new
* LdapAuthenticationProvider(authenticator); Authentication a =
* provider.authenticate(new
* UsernamePasswordAuthenticationToken("expireme","password")); PasswordPolicyData ud
* = (LdapUserDetailsImpl) a.getPrincipal(); assertTrue(ud.getTimeBeforeExpiration() <
* Integer.MAX_VALUE && ud.getTimeBeforeExpiration() > 0); }
*/
}
@@ -17,46 +17,53 @@ import java.util.*;
* @author Luke Taylor
*/
public class PasswordPolicyAwareContextSourceTests {
private PasswordPolicyAwareContextSource ctxSource;
private final LdapContext ctx = mock(LdapContext.class);
private PasswordPolicyAwareContextSource ctxSource;
private final LdapContext ctx = mock(LdapContext.class);
@Before
public void setUp() throws Exception {
reset(ctx);
ctxSource = new PasswordPolicyAwareContextSource("ldap://blah:789/dc=springframework,dc=org") {
@Override
protected DirContext createContext(Hashtable env) {
if ("manager".equals(env.get(Context.SECURITY_PRINCIPAL))) {
return ctx;
}
@Before
public void setUp() throws Exception {
reset(ctx);
ctxSource = new PasswordPolicyAwareContextSource(
"ldap://blah:789/dc=springframework,dc=org") {
@Override
protected DirContext createContext(Hashtable env) {
if ("manager".equals(env.get(Context.SECURITY_PRINCIPAL))) {
return ctx;
}
return null;
}
};
ctxSource.setUserDn("manager");
ctxSource.setPassword("password");
ctxSource.afterPropertiesSet();
}
return null;
}
};
ctxSource.setUserDn("manager");
ctxSource.setPassword("password");
ctxSource.afterPropertiesSet();
}
@Test
public void contextIsReturnedWhenNoControlsAreSetAndReconnectIsSuccessful() throws Exception {
assertNotNull(ctxSource.getContext("user", "ignored"));
}
@Test
public void contextIsReturnedWhenNoControlsAreSetAndReconnectIsSuccessful()
throws Exception {
assertNotNull(ctxSource.getContext("user", "ignored"));
}
@Test(expected=UncategorizedLdapException.class)
public void standardExceptionIsPropagatedWhenExceptionRaisedAndNoControlsAreSet() throws Exception {
doThrow(new NamingException("some LDAP exception")).when(ctx).reconnect(any(Control[].class));
@Test(expected = UncategorizedLdapException.class)
public void standardExceptionIsPropagatedWhenExceptionRaisedAndNoControlsAreSet()
throws Exception {
doThrow(new NamingException("some LDAP exception")).when(ctx).reconnect(
any(Control[].class));
ctxSource.getContext("user", "ignored");
}
ctxSource.getContext("user", "ignored");
}
@Test(expected=PasswordPolicyException.class)
public void lockedPasswordPolicyControlRaisesPasswordPolicyException() throws Exception {
when(ctx.getResponseControls()).thenReturn(new Control[] {
new PasswordPolicyResponseControl(PasswordPolicyResponseControlTests.OPENLDAP_LOCKED_CTRL) });
@Test(expected = PasswordPolicyException.class)
public void lockedPasswordPolicyControlRaisesPasswordPolicyException()
throws Exception {
when(ctx.getResponseControls()).thenReturn(
new Control[] { new PasswordPolicyResponseControl(
PasswordPolicyResponseControlTests.OPENLDAP_LOCKED_CTRL) });
doThrow(new NamingException("locked message")).when(ctx).reconnect(any(Control[].class));
doThrow(new NamingException("locked message")).when(ctx).reconnect(
any(Control[].class));
ctxSource.getContext("user", "ignored");
}
ctxSource.getContext("user", "ignored");
}
}
@@ -13,24 +13,26 @@ import java.util.*;
*/
public class PasswordPolicyControlFactoryTests {
@Test
public void returnsNullForUnrecognisedOID() throws Exception {
PasswordPolicyControlFactory ctrlFactory = new PasswordPolicyControlFactory();
Control wrongCtrl = mock(Control.class);
@Test
public void returnsNullForUnrecognisedOID() throws Exception {
PasswordPolicyControlFactory ctrlFactory = new PasswordPolicyControlFactory();
Control wrongCtrl = mock(Control.class);
when(wrongCtrl.getID()).thenReturn("wrongId");
assertNull(ctrlFactory.getControlInstance(wrongCtrl));
}
when(wrongCtrl.getID()).thenReturn("wrongId");
assertNull(ctrlFactory.getControlInstance(wrongCtrl));
}
@Test
public void returnsControlForCorrectOID() throws Exception {
PasswordPolicyControlFactory ctrlFactory = new PasswordPolicyControlFactory();
Control control = mock(Control.class);
@Test
public void returnsControlForCorrectOID() throws Exception {
PasswordPolicyControlFactory ctrlFactory = new PasswordPolicyControlFactory();
Control control = mock(Control.class);
when(control.getID()).thenReturn(PasswordPolicyControl.OID);
when(control.getEncodedValue()).thenReturn(PasswordPolicyResponseControlTests.OPENLDAP_LOCKED_CTRL);
Control result = ctrlFactory.getControlInstance(control);
assertNotNull(result);
assertTrue(Arrays.equals(PasswordPolicyResponseControlTests.OPENLDAP_LOCKED_CTRL, result.getEncodedValue()));
}
when(control.getID()).thenReturn(PasswordPolicyControl.OID);
when(control.getEncodedValue()).thenReturn(
PasswordPolicyResponseControlTests.OPENLDAP_LOCKED_CTRL);
Control result = ctrlFactory.getControlInstance(control);
assertNotNull(result);
assertTrue(Arrays.equals(PasswordPolicyResponseControlTests.OPENLDAP_LOCKED_CTRL,
result.getEncodedValue()));
}
}
@@ -28,106 +28,113 @@ import java.util.*;
* @author Luke Taylor
*/
public class PasswordPolicyResponseControlTests {
//~ Methods ========================================================================================================
// ~ Methods
// ========================================================================================================
/**
* Useful method for obtaining data from a server for use in tests
*/
// public void testAgainstServer() throws Exception {
// Hashtable env = new Hashtable();
// env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// env.put(Context.PROVIDER_URL, "ldap://gorille:389/");
// env.put(Context.SECURITY_AUTHENTICATION, "simple");
// env.put(Context.SECURITY_PRINCIPAL, "cn=manager,dc=security,dc=org");
// env.put(Context.SECURITY_CREDENTIALS, "security");
// env.put(LdapContext.CONTROL_FACTORIES, PasswordPolicyControlFactory.class.getName());
//
// InitialLdapContext ctx = new InitialLdapContext(env, null);
//
// Control[] rctls = { new PasswordPolicyControl(false) };
//
// ctx.setRequestControls(rctls);
//
// try {
// ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, "uid=bob,ou=people,dc=security,dc=org" );
// ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, "bobspassword");
// Object o = ctx.lookup("");
//
// System.out.println(o);
//
// } catch(NamingException ne) {
// // Ok.
// System.err.println(ne);
// }
//
// PasswordPolicyResponseControl ctrl = getPPolicyResponseCtl(ctx);
// System.out.println(ctrl);
//
// assertNotNull(ctrl);
//
// //com.sun.jndi.ldap.LdapPoolManager.showStats(System.out);
// }
/**
* Useful method for obtaining data from a server for use in tests
*/
// public void testAgainstServer() throws Exception {
// Hashtable env = new Hashtable();
// env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// env.put(Context.PROVIDER_URL, "ldap://gorille:389/");
// env.put(Context.SECURITY_AUTHENTICATION, "simple");
// env.put(Context.SECURITY_PRINCIPAL, "cn=manager,dc=security,dc=org");
// env.put(Context.SECURITY_CREDENTIALS, "security");
// env.put(LdapContext.CONTROL_FACTORIES,
// PasswordPolicyControlFactory.class.getName());
//
// InitialLdapContext ctx = new InitialLdapContext(env, null);
//
// Control[] rctls = { new PasswordPolicyControl(false) };
//
// ctx.setRequestControls(rctls);
//
// try {
// ctx.addToEnvironment(Context.SECURITY_PRINCIPAL,
// "uid=bob,ou=people,dc=security,dc=org" );
// ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, "bobspassword");
// Object o = ctx.lookup("");
//
// System.out.println(o);
//
// } catch(NamingException ne) {
// // Ok.
// System.err.println(ne);
// }
//
// PasswordPolicyResponseControl ctrl = getPPolicyResponseCtl(ctx);
// System.out.println(ctrl);
//
// assertNotNull(ctrl);
//
// //com.sun.jndi.ldap.LdapPoolManager.showStats(System.out);
// }
// private PasswordPolicyResponseControl getPPolicyResponseCtl(InitialLdapContext ctx)
// throws NamingException {
// Control[] ctrls = ctx.getResponseControls();
//
// for (int i = 0; ctrls != null && i < ctrls.length; i++) {
// if (ctrls[i] instanceof PasswordPolicyResponseControl) {
// return (PasswordPolicyResponseControl) ctrls[i];
// }
// }
//
// return null;
// }
// private PasswordPolicyResponseControl getPPolicyResponseCtl(InitialLdapContext ctx) throws NamingException {
// Control[] ctrls = ctx.getResponseControls();
//
// for (int i = 0; ctrls != null && i < ctrls.length; i++) {
// if (ctrls[i] instanceof PasswordPolicyResponseControl) {
// return (PasswordPolicyResponseControl) ctrls[i];
// }
// }
//
// return null;
// }
@Test
public void openLDAP33SecondsTillPasswordExpiryCtrlIsParsedCorrectly() {
byte[] ctrlBytes = { 0x30, 0x05, (byte) 0xA0, 0x03, (byte) 0xA0, 0x1, 0x21 };
@Test
public void openLDAP33SecondsTillPasswordExpiryCtrlIsParsedCorrectly() {
byte[] ctrlBytes = {0x30, 0x05, (byte) 0xA0, 0x03, (byte) 0xA0, 0x1, 0x21};
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(ctrlBytes);
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(ctrlBytes);
assertTrue(ctrl.hasWarning());
assertEquals(33, ctrl.getTimeBeforeExpiration());
}
assertTrue(ctrl.hasWarning());
assertEquals(33, ctrl.getTimeBeforeExpiration());
}
@Test
public void openLDAP496GraceLoginsRemainingCtrlIsParsedCorrectly() {
byte[] ctrlBytes = { 0x30, 0x06, (byte) 0xA0, 0x04, (byte) 0xA1, 0x02, 0x01,
(byte) 0xF0 };
@Test
public void openLDAP496GraceLoginsRemainingCtrlIsParsedCorrectly() {
byte[] ctrlBytes = {0x30, 0x06, (byte) 0xA0, 0x04, (byte) 0xA1, 0x02, 0x01, (byte) 0xF0};
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(ctrlBytes);
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(ctrlBytes);
assertTrue(ctrl.hasWarning());
assertEquals(496, ctrl.getGraceLoginsRemaining());
}
assertTrue(ctrl.hasWarning());
assertEquals(496, ctrl.getGraceLoginsRemaining());
}
static final byte[] OPENLDAP_5_LOGINS_REMAINING_CTRL = { 0x30, 0x05, (byte) 0xA0,
0x03, (byte) 0xA1, 0x01, 0x05 };
static final byte[] OPENLDAP_5_LOGINS_REMAINING_CTRL = {0x30, 0x05, (byte) 0xA0, 0x03, (byte) 0xA1, 0x01, 0x05};
@Test
public void openLDAP5GraceLoginsRemainingCtrlIsParsedCorrectly() {
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(
OPENLDAP_5_LOGINS_REMAINING_CTRL);
@Test
public void openLDAP5GraceLoginsRemainingCtrlIsParsedCorrectly() {
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(OPENLDAP_5_LOGINS_REMAINING_CTRL);
assertTrue(ctrl.hasWarning());
assertEquals(5, ctrl.getGraceLoginsRemaining());
}
assertTrue(ctrl.hasWarning());
assertEquals(5, ctrl.getGraceLoginsRemaining());
}
static final byte[] OPENLDAP_LOCKED_CTRL = { 0x30, 0x03, (byte) 0xA1, 0x01, 0x01 };
static final byte[] OPENLDAP_LOCKED_CTRL = {0x30, 0x03, (byte) 0xA1, 0x01, 0x01};
@Test
public void openLDAPAccountLockedCtrlIsParsedCorrectly() {
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(
OPENLDAP_LOCKED_CTRL);
@Test
public void openLDAPAccountLockedCtrlIsParsedCorrectly() {
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(OPENLDAP_LOCKED_CTRL);
assertTrue(ctrl.hasError() && ctrl.isLocked());
assertFalse(ctrl.hasWarning());
}
assertTrue(ctrl.hasError() && ctrl.isLocked());
assertFalse(ctrl.hasWarning());
}
@Test
public void openLDAPPasswordExpiredCtrlIsParsedCorrectly() {
byte[] ctrlBytes = { 0x30, 0x03, (byte) 0xA1, 0x01, 0x00 };
@Test
public void openLDAPPasswordExpiredCtrlIsParsedCorrectly() {
byte[] ctrlBytes = {0x30, 0x03, (byte) 0xA1, 0x01, 0x00};
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(ctrlBytes);
PasswordPolicyResponseControl ctrl = new PasswordPolicyResponseControl(ctrlBytes);
assertTrue(ctrl.hasError() && ctrl.isExpired());
assertFalse(ctrl.hasWarning());
}
assertTrue(ctrl.hasError() && ctrl.isExpired());
assertFalse(ctrl.hasWarning());
}
}
@@ -14,122 +14,127 @@ import org.springframework.ldap.core.DistinguishedName;
*/
public class InetOrgPersonTests {
@Test
public void testUsernameIsMappedFromContextUidIfNotSet() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
@Test
public void testUsernameIsMappedFromContextUidIfNotSet() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
assertEquals("ghengis", p.getUsername());
}
assertEquals("ghengis", p.getUsername());
}
@Test
public void hashLookupViaEqualObjectRetrievesOriginal() throws Exception {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p2 = (InetOrgPerson) essence.createUserDetails();
Set<InetOrgPerson> set = new HashSet<InetOrgPerson>();
set.add(p);
assertTrue(set.contains(p2));
}
@Test
public void hashLookupViaEqualObjectRetrievesOriginal() throws Exception {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p2 = (InetOrgPerson) essence.createUserDetails();
Set<InetOrgPerson> set = new HashSet<InetOrgPerson>();
set.add(p);
assertTrue(set.contains(p2));
}
@Test
public void usernameIsDifferentFromContextUidIfSet() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
essence.setUsername("joe");
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
@Test
public void usernameIsDifferentFromContextUidIfSet() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
essence.setUsername("joe");
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
assertEquals("joe", p.getUsername());
assertEquals("ghengis", p.getUid());
}
assertEquals("joe", p.getUsername());
assertEquals("ghengis", p.getUid());
}
@Test
public void attributesMapCorrectlyFromContext() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
@Test
public void attributesMapCorrectlyFromContext() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
assertEquals("HORS1", p.getCarLicense());
assertEquals("ghengis@mongolia", p.getMail());
assertEquals("Ghengis", p.getGivenName());
assertEquals("Khan", p.getSn());
assertEquals("Ghengis Khan", p.getCn()[0]);
assertEquals("00001", p.getEmployeeNumber());
assertEquals("+442075436521", p.getTelephoneNumber());
assertEquals("Steppes", p.getHomePostalAddress());
assertEquals("+467575436521", p.getHomePhone());
assertEquals("Hordes", p.getO());
assertEquals("Horde1", p.getOu());
assertEquals("On the Move", p.getPostalAddress());
assertEquals("Changes Frequently", p.getPostalCode());
assertEquals("Yurt 1", p.getRoomNumber());
assertEquals("Westward Avenue", p.getStreet());
assertEquals("Scary", p.getDescription());
assertEquals("Ghengis McCann", p.getDisplayName());
assertEquals("G", p.getInitials());
}
assertEquals("HORS1", p.getCarLicense());
assertEquals("ghengis@mongolia", p.getMail());
assertEquals("Ghengis", p.getGivenName());
assertEquals("Khan", p.getSn());
assertEquals("Ghengis Khan", p.getCn()[0]);
assertEquals("00001", p.getEmployeeNumber());
assertEquals("+442075436521", p.getTelephoneNumber());
assertEquals("Steppes", p.getHomePostalAddress());
assertEquals("+467575436521", p.getHomePhone());
assertEquals("Hordes", p.getO());
assertEquals("Horde1", p.getOu());
assertEquals("On the Move", p.getPostalAddress());
assertEquals("Changes Frequently", p.getPostalCode());
assertEquals("Yurt 1", p.getRoomNumber());
assertEquals("Westward Avenue", p.getStreet());
assertEquals("Scary", p.getDescription());
assertEquals("Ghengis McCann", p.getDisplayName());
assertEquals("G", p.getInitials());
}
@Test
public void testPasswordIsSetFromContextUserPassword() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
@Test
public void testPasswordIsSetFromContextUserPassword() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
assertEquals("pillage", p.getPassword());
}
assertEquals("pillage", p.getPassword());
}
@Test
public void mappingBackToContextMatchesOriginalData() {
DirContextAdapter ctx1 = createUserContext();
DirContextAdapter ctx2 = new DirContextAdapter();
ctx1.setAttributeValues("objectclass", new String[] {"top", "person", "organizationalPerson", "inetOrgPerson"});
ctx2.setDn(new DistinguishedName("ignored=ignored"));
InetOrgPerson p = (InetOrgPerson) (new InetOrgPerson.Essence(ctx1)).createUserDetails();
p.populateContext(ctx2);
@Test
public void mappingBackToContextMatchesOriginalData() {
DirContextAdapter ctx1 = createUserContext();
DirContextAdapter ctx2 = new DirContextAdapter();
ctx1.setAttributeValues("objectclass", new String[] { "top", "person",
"organizationalPerson", "inetOrgPerson" });
ctx2.setDn(new DistinguishedName("ignored=ignored"));
InetOrgPerson p = (InetOrgPerson) (new InetOrgPerson.Essence(ctx1))
.createUserDetails();
p.populateContext(ctx2);
assertEquals(ctx1, ctx2);
}
assertEquals(ctx1, ctx2);
}
@Test
public void copyMatchesOriginalData() {
DirContextAdapter ctx1 = createUserContext();
DirContextAdapter ctx2 = new DirContextAdapter();
ctx2.setDn(new DistinguishedName("ignored=ignored"));
ctx1.setAttributeValues("objectclass", new String[] {"top", "person", "organizationalPerson", "inetOrgPerson"});
InetOrgPerson p = (InetOrgPerson) (new InetOrgPerson.Essence(ctx1)).createUserDetails();
InetOrgPerson p2 = (InetOrgPerson) new InetOrgPerson.Essence(p).createUserDetails();
p2.populateContext(ctx2);
@Test
public void copyMatchesOriginalData() {
DirContextAdapter ctx1 = createUserContext();
DirContextAdapter ctx2 = new DirContextAdapter();
ctx2.setDn(new DistinguishedName("ignored=ignored"));
ctx1.setAttributeValues("objectclass", new String[] { "top", "person",
"organizationalPerson", "inetOrgPerson" });
InetOrgPerson p = (InetOrgPerson) (new InetOrgPerson.Essence(ctx1))
.createUserDetails();
InetOrgPerson p2 = (InetOrgPerson) new InetOrgPerson.Essence(p)
.createUserDetails();
p2.populateContext(ctx2);
assertEquals(ctx1, ctx2);
}
assertEquals(ctx1, ctx2);
}
private DirContextAdapter createUserContext() {
DirContextAdapter ctx = new DirContextAdapter();
private DirContextAdapter createUserContext() {
DirContextAdapter ctx = new DirContextAdapter();
ctx.setDn(new DistinguishedName("ignored=ignored"));
ctx.setAttributeValue("uid", "ghengis");
ctx.setAttributeValue("userPassword", "pillage");
ctx.setAttributeValue("carLicense", "HORS1");
ctx.setAttributeValue("cn", "Ghengis Khan");
ctx.setAttributeValue("description", "Scary");
ctx.setAttributeValue("destinationIndicator", "West");
ctx.setAttributeValue("displayName", "Ghengis McCann");
ctx.setAttributeValue("givenName", "Ghengis");
ctx.setAttributeValue("homePhone", "+467575436521");
ctx.setAttributeValue("initials", "G");
ctx.setAttributeValue("employeeNumber", "00001");
ctx.setAttributeValue("homePostalAddress", "Steppes");
ctx.setAttributeValue("mail", "ghengis@mongolia");
ctx.setAttributeValue("mobile", "always");
ctx.setAttributeValue("o", "Hordes");
ctx.setAttributeValue("ou", "Horde1");
ctx.setAttributeValue("postalAddress", "On the Move");
ctx.setAttributeValue("postalCode", "Changes Frequently");
ctx.setAttributeValue("roomNumber", "Yurt 1");
ctx.setAttributeValue("roomNumber", "Yurt 1");
ctx.setAttributeValue("sn", "Khan");
ctx.setAttributeValue("street", "Westward Avenue");
ctx.setAttributeValue("telephoneNumber", "+442075436521");
ctx.setDn(new DistinguishedName("ignored=ignored"));
ctx.setAttributeValue("uid", "ghengis");
ctx.setAttributeValue("userPassword", "pillage");
ctx.setAttributeValue("carLicense", "HORS1");
ctx.setAttributeValue("cn", "Ghengis Khan");
ctx.setAttributeValue("description", "Scary");
ctx.setAttributeValue("destinationIndicator", "West");
ctx.setAttributeValue("displayName", "Ghengis McCann");
ctx.setAttributeValue("givenName", "Ghengis");
ctx.setAttributeValue("homePhone", "+467575436521");
ctx.setAttributeValue("initials", "G");
ctx.setAttributeValue("employeeNumber", "00001");
ctx.setAttributeValue("homePostalAddress", "Steppes");
ctx.setAttributeValue("mail", "ghengis@mongolia");
ctx.setAttributeValue("mobile", "always");
ctx.setAttributeValue("o", "Hordes");
ctx.setAttributeValue("ou", "Horde1");
ctx.setAttributeValue("postalAddress", "On the Move");
ctx.setAttributeValue("postalCode", "Changes Frequently");
ctx.setAttributeValue("roomNumber", "Yurt 1");
ctx.setAttributeValue("roomNumber", "Yurt 1");
ctx.setAttributeValue("sn", "Khan");
ctx.setAttributeValue("street", "Westward Avenue");
ctx.setAttributeValue("telephoneNumber", "+442075436521");
return ctx;
}
return ctx;
}
}
@@ -17,38 +17,41 @@ import static org.junit.Assert.assertNotNull;
*/
public class LdapAuthorityTests {
public static final String DN = "cn=filip,ou=Users,dc=test,dc=com";
LdapAuthority authority;
public static final String DN = "cn=filip,ou=Users,dc=test,dc=com";
LdapAuthority authority;
@Before
public void setUp() {
Map<String, List<String>> attributes = new HashMap<String, List<String>>();
attributes.put(SpringSecurityLdapTemplate.DN_KEY, Arrays.asList(DN));
attributes.put("mail", Arrays.asList("filip@ldap.test.org", "filip@ldap.test2.org"));
authority = new LdapAuthority("testRole", DN, attributes);
}
@Before
public void setUp() {
Map<String, List<String>> attributes = new HashMap<String, List<String>>();
attributes.put(SpringSecurityLdapTemplate.DN_KEY, Arrays.asList(DN));
attributes.put("mail",
Arrays.asList("filip@ldap.test.org", "filip@ldap.test2.org"));
authority = new LdapAuthority("testRole", DN, attributes);
}
@Test
public void testGetDn() throws Exception {
assertEquals(DN, authority.getDn());
assertNotNull(authority.getAttributeValues(SpringSecurityLdapTemplate.DN_KEY));
assertEquals(1, authority.getAttributeValues(SpringSecurityLdapTemplate.DN_KEY).size());
assertEquals(DN, authority.getFirstAttributeValue(SpringSecurityLdapTemplate.DN_KEY));
}
@Test
public void testGetDn() throws Exception {
assertEquals(DN, authority.getDn());
assertNotNull(authority.getAttributeValues(SpringSecurityLdapTemplate.DN_KEY));
assertEquals(1, authority.getAttributeValues(SpringSecurityLdapTemplate.DN_KEY)
.size());
assertEquals(DN,
authority.getFirstAttributeValue(SpringSecurityLdapTemplate.DN_KEY));
}
@Test
public void testGetAttributes() throws Exception {
assertNotNull(authority.getAttributes());
assertNotNull(authority.getAttributeValues("mail"));
assertEquals(2, authority.getAttributeValues("mail").size());
assertEquals("filip@ldap.test.org", authority.getFirstAttributeValue("mail"));
assertEquals("filip@ldap.test.org", authority.getAttributeValues("mail").get(0));
assertEquals("filip@ldap.test2.org", authority.getAttributeValues("mail").get(1));
}
@Test
public void testGetAttributes() throws Exception {
assertNotNull(authority.getAttributes());
assertNotNull(authority.getAttributeValues("mail"));
assertEquals(2, authority.getAttributeValues("mail").size());
assertEquals("filip@ldap.test.org", authority.getFirstAttributeValue("mail"));
assertEquals("filip@ldap.test.org", authority.getAttributeValues("mail").get(0));
assertEquals("filip@ldap.test2.org", authority.getAttributeValues("mail").get(1));
}
@Test
public void testGetAuthority() throws Exception {
assertNotNull(authority.getAuthority());
assertEquals("testRole", authority.getAuthority());
}
@Test
public void testGetAuthority() throws Exception {
assertNotNull(authority.getAuthority());
assertEquals("testRole", authority.getAuthority());
}
}
@@ -31,55 +31,61 @@ import org.springframework.security.core.authority.AuthorityUtils;
*/
public class LdapUserDetailsMapperTests extends TestCase {
public void testMultipleRoleAttributeValuesAreMappedToAuthorities() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
mapper.setConvertToUpperCase(false);
mapper.setRolePrefix("");
public void testMultipleRoleAttributeValuesAreMappedToAuthorities() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
mapper.setConvertToUpperCase(false);
mapper.setRolePrefix("");
mapper.setRoleAttributes(new String[] {"userRole"});
mapper.setRoleAttributes(new String[] { "userRole" });
DirContextAdapter ctx = new DirContextAdapter();
DirContextAdapter ctx = new DirContextAdapter();
ctx.setAttributeValues("userRole", new String[] {"X", "Y", "Z"});
ctx.setAttributeValue("uid", "ani");
ctx.setAttributeValues("userRole", new String[] { "X", "Y", "Z" });
ctx.setAttributeValue("uid", "ani");
LdapUserDetailsImpl user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx, "ani", AuthorityUtils.NO_AUTHORITIES);
LdapUserDetailsImpl user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx,
"ani", AuthorityUtils.NO_AUTHORITIES);
assertEquals(3, user.getAuthorities().size());
}
assertEquals(3, user.getAuthorities().size());
}
/**
* SEC-303. Non-retrieved role attribute causes NullPointerException
*/
public void testNonRetrievedRoleAttributeIsIgnored() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
/**
* SEC-303. Non-retrieved role attribute causes NullPointerException
*/
public void testNonRetrievedRoleAttributeIsIgnored() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
mapper.setRoleAttributes(new String[] {"userRole", "nonRetrievedAttribute"});
mapper.setRoleAttributes(new String[] { "userRole", "nonRetrievedAttribute" });
BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("userRole", "x"));
BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("userRole", "x"));
DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName("cn=someName"));
ctx.setAttributeValue("uid", "ani");
DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName(
"cn=someName"));
ctx.setAttributeValue("uid", "ani");
LdapUserDetailsImpl user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx, "ani", AuthorityUtils.NO_AUTHORITIES);
LdapUserDetailsImpl user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx,
"ani", AuthorityUtils.NO_AUTHORITIES);
assertEquals(1, user.getAuthorities().size());
assertTrue(AuthorityUtils.authorityListToSet(user.getAuthorities()).contains("ROLE_X"));
}
assertEquals(1, user.getAuthorities().size());
assertTrue(AuthorityUtils.authorityListToSet(user.getAuthorities()).contains(
"ROLE_X"));
}
public void testPasswordAttributeIsMappedCorrectly() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
public void testPasswordAttributeIsMappedCorrectly() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
mapper.setPasswordAttributeName("myappsPassword");
BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("myappsPassword", "mypassword".getBytes()));
mapper.setPasswordAttributeName("myappsPassword");
BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("myappsPassword", "mypassword".getBytes()));
DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName("cn=someName"));
ctx.setAttributeValue("uid", "ani");
DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName(
"cn=someName"));
ctx.setAttributeValue("uid", "ani");
LdapUserDetails user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx, "ani", AuthorityUtils.NO_AUTHORITIES);
LdapUserDetails user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx,
"ani", AuthorityUtils.NO_AUTHORITIES);
assertEquals("mypassword", user.getPassword());
}
assertEquals("mypassword", user.getPassword());
}
}
@@ -22,43 +22,48 @@ import org.springframework.security.ldap.authentication.NullLdapAuthoritiesPopul
*/
public class LdapUserDetailsServiceTests {
@Test(expected = IllegalArgumentException.class)
public void rejectsNullSearchObject() {
new LdapUserDetailsService(null, new NullLdapAuthoritiesPopulator());
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullSearchObject() {
new LdapUserDetailsService(null, new NullLdapAuthoritiesPopulator());
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullAuthoritiesPopulator() {
new LdapUserDetailsService(new MockUserSearch(), null);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullAuthoritiesPopulator() {
new LdapUserDetailsService(new MockUserSearch(), null);
}
@Test
public void correctAuthoritiesAreReturned() {
DirContextAdapter userData = new DirContextAdapter(new DistinguishedName("uid=joe"));
@Test
public void correctAuthoritiesAreReturned() {
DirContextAdapter userData = new DirContextAdapter(new DistinguishedName(
"uid=joe"));
LdapUserDetailsService service =
new LdapUserDetailsService(new MockUserSearch(userData), new MockAuthoritiesPopulator());
service.setUserDetailsMapper(new LdapUserDetailsMapper());
LdapUserDetailsService service = new LdapUserDetailsService(new MockUserSearch(
userData), new MockAuthoritiesPopulator());
service.setUserDetailsMapper(new LdapUserDetailsMapper());
UserDetails user = service.loadUserByUsername("doesntmatterwegetjoeanyway");
UserDetails user = service.loadUserByUsername("doesntmatterwegetjoeanyway");
Set<String> authorities = AuthorityUtils.authorityListToSet(user.getAuthorities());
assertEquals(1, authorities.size());
assertTrue(authorities.contains("ROLE_FROM_POPULATOR"));
}
Set<String> authorities = AuthorityUtils
.authorityListToSet(user.getAuthorities());
assertEquals(1, authorities.size());
assertTrue(authorities.contains("ROLE_FROM_POPULATOR"));
}
@Test
public void nullPopulatorConstructorReturnsEmptyAuthoritiesList() throws Exception {
DirContextAdapter userData = new DirContextAdapter(new DistinguishedName("uid=joe"));
@Test
public void nullPopulatorConstructorReturnsEmptyAuthoritiesList() throws Exception {
DirContextAdapter userData = new DirContextAdapter(new DistinguishedName(
"uid=joe"));
LdapUserDetailsService service = new LdapUserDetailsService(new MockUserSearch(userData));
UserDetails user = service.loadUserByUsername("doesntmatterwegetjoeanyway");
assertEquals(0, user.getAuthorities().size());
}
LdapUserDetailsService service = new LdapUserDetailsService(new MockUserSearch(
userData));
UserDetails user = service.loadUserByUsername("doesntmatterwegetjoeanyway");
assertEquals(0, user.getAuthorities().size());
}
class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator {
public Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations userCtx, String username) {
return AuthorityUtils.createAuthorityList("ROLE_FROM_POPULATOR");
}
}
class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator {
public Collection<GrantedAuthority> getGrantedAuthorities(
DirContextOperations userCtx, String username) {
return AuthorityUtils.createAuthorityList("ROLE_FROM_POPULATOR");
}
}
}
@@ -19,18 +19,20 @@ import org.springframework.security.ldap.authentication.UserDetailsServiceLdapAu
*/
public class UserDetailsServiceLdapAuthoritiesPopulatorTests {
@Test
public void delegationToUserDetailsServiceReturnsCorrectRoles() throws Exception {
UserDetailsService uds = mock(UserDetailsService.class);
UserDetails user = mock(UserDetails.class);
when(uds.loadUserByUsername("joe")).thenReturn(user);
List authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
when(user.getAuthorities()).thenReturn(authorities);
@Test
public void delegationToUserDetailsServiceReturnsCorrectRoles() throws Exception {
UserDetailsService uds = mock(UserDetailsService.class);
UserDetails user = mock(UserDetails.class);
when(uds.loadUserByUsername("joe")).thenReturn(user);
List authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
when(user.getAuthorities()).thenReturn(authorities);
UserDetailsServiceLdapAuthoritiesPopulator populator = new UserDetailsServiceLdapAuthoritiesPopulator(uds);
Collection<? extends GrantedAuthority> auths = populator.getGrantedAuthorities(new DirContextAdapter(), "joe");
UserDetailsServiceLdapAuthoritiesPopulator populator = new UserDetailsServiceLdapAuthoritiesPopulator(
uds);
Collection<? extends GrantedAuthority> auths = populator.getGrantedAuthorities(
new DirContextAdapter(), "joe");
assertEquals(1, auths.size());
assertTrue(AuthorityUtils.authorityListToSet(auths).contains("ROLE_USER"));
}
assertEquals(1, auths.size());
assertTrue(AuthorityUtils.authorityListToSet(auths).contains("ROLE_USER"));
}
}