SEC-2915: Updated Java Code Formatting
This commit is contained in:
+16
-14
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+89
-108
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+115
-99
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+155
-155
@@ -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 });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+98
-82
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+98
-87
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
+60
-52
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+76
-59
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+111
-97
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
+140
-133
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+106
-87
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+22
-22
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+115
-99
@@ -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/> '<' [less than] -
|
||||
* "\<" <br/> '>' [greater than] - "\>" <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/>
|
||||
* '<' [less than] - "\<" <br/>
|
||||
* '>' [greater than] - "\>" <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+296
-266
@@ -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<String,String[]>
|
||||
* 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<String,String[]>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
+101
-85
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+101
-87
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+100
-88
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+121
-104
@@ -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>
|
||||
* <bean id="contextSource"
|
||||
* class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
|
||||
@@ -74,7 +76,7 @@ import java.util.*;
|
||||
* <property name="userDn" value="cn=manager,dc=springframework,dc=org"/>
|
||||
* <property name="password" value="password"/>
|
||||
* </bean>
|
||||
*
|
||||
*
|
||||
* <bean id="ldapAuthProvider"
|
||||
* class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
|
||||
* <constructor-arg>
|
||||
@@ -91,22 +93,24 @@ import java.util.*;
|
||||
* </bean>
|
||||
* </constructor-arg>
|
||||
* </bean>
|
||||
*</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=<user-login-name>,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=<user's-DN>)</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=<user-login-name>,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=<user's-DN>)</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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -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);
|
||||
}
|
||||
|
||||
+170
-166
@@ -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/> '<' [less than] -
|
||||
* "\<" <br/> '>' [greater than] - "\>" <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/>
|
||||
* '<' [less than] - "\<" <br/>
|
||||
* '>' [greater than] - "\>" <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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+111
-98
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+47
-41
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+13
-11
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+18
-14
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+290
-255
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+56
-50
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+49
-46
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-16
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-16
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ package org.springframework.security.ldap.ppolicy;
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface PasswordPolicyData {
|
||||
int getTimeBeforeExpiration();
|
||||
int getTimeBeforeExpiration();
|
||||
|
||||
int getGraceLoginsRemaining();
|
||||
int getGraceLoginsRemaining();
|
||||
}
|
||||
|
||||
+27
-25
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+279
-254
@@ -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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
+131
-113
@@ -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;
|
||||
}
|
||||
|
||||
+223
-204
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+270
-248
@@ -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>
|
||||
* <bean id="ldapAuthoritiesPopulator"
|
||||
* class="org.springframework.security.authentication.ldap.populator.DefaultLdapAuthoritiesPopulator">
|
||||
@@ -82,291 +86,309 @@ import java.util.Set;
|
||||
* <property name="convertToUpperCase" value="true"/>
|
||||
* </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;
|
||||
}
|
||||
}
|
||||
|
||||
+207
-207
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-12
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-13
@@ -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);
|
||||
}
|
||||
|
||||
+109
-111
@@ -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 + '\'' + '}';
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -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();
|
||||
}
|
||||
|
||||
+191
-177
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+348
-321
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+127
-120
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+34
-29
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+152
-134
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+12
-11
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-17
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
+43
-40
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+27
-24
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+158
-129
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+89
-71
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-13
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-22
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
+409
-363
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
-44
@@ -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); }
|
||||
*/
|
||||
}
|
||||
|
||||
+41
-34
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+19
-17
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
+92
-85
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+107
-102
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+33
-30
@@ -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());
|
||||
}
|
||||
}
|
||||
+41
-35
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+36
-31
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-12
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user