SEC-1124: Refactored LDAP code into separate module
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
package org.springframework.security.config.ldap;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.security.config.LdapServerBeanDefinitionParser;
|
||||
import org.springframework.util.Assert;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.directory.server.configuration.MutableServerStartupConfiguration;
|
||||
import org.apache.directory.server.jndi.ServerContextFactory;
|
||||
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
|
||||
import org.apache.directory.server.core.configuration.ShutdownConfiguration;
|
||||
import org.apache.directory.server.core.DirectoryService;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import java.util.Properties;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides lifecycle services for the embedded apacheDS server defined by the supplied configuration.
|
||||
* Used by {@link 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.
|
||||
* </p>
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle, ApplicationContextAware {
|
||||
private Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private MutableServerStartupConfiguration configuration;
|
||||
private ApplicationContext ctxt;
|
||||
private File workingDir;
|
||||
|
||||
private ContextSource contextSource;
|
||||
private boolean running;
|
||||
private String ldifResources;
|
||||
|
||||
public ApacheDSContainer(MutableServerStartupConfiguration config, ContextSource contextSource, String ldifs) {
|
||||
this.configuration = config;
|
||||
this.contextSource = contextSource;
|
||||
this.ldifResources = ldifs;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (workingDir == null) {
|
||||
String apacheWorkDir = System.getProperty("apacheDSWorkDir");
|
||||
|
||||
if (apacheWorkDir == null) {
|
||||
apacheWorkDir = System.getProperty("java.io.tmpdir") + File.separator + "apacheds-spring-security";
|
||||
}
|
||||
|
||||
setWorkingDirectory(new File(apacheWorkDir));
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
public void destroy() throws Exception {
|
||||
stop();
|
||||
}
|
||||
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
ctxt = applicationContext;
|
||||
}
|
||||
|
||||
private boolean deleteDir(File dir) {
|
||||
if (dir.isDirectory()) {
|
||||
String[] children = dir.list();
|
||||
for (int i=0; i < children.length; i++) {
|
||||
boolean success = deleteDir(new File(dir, children[i]));
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dir.delete();
|
||||
}
|
||||
|
||||
public void setWorkingDirectory(File workingDir) {
|
||||
Assert.notNull(workingDir);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
this.workingDir = workingDir;
|
||||
|
||||
configuration.setWorkingDirectory(workingDir);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void start() {
|
||||
if (isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DirectoryService ds = DirectoryService.getInstance(configuration.getInstanceId());
|
||||
|
||||
if (ds.isStarted()) {
|
||||
throw new IllegalStateException("A DirectoryService with Id '" + configuration.getInstanceId() + "' is already running.");
|
||||
}
|
||||
|
||||
logger.info("Starting directory server with Id '" + configuration.getInstanceId() + "'");
|
||||
Properties env = new Properties();
|
||||
|
||||
env.setProperty(Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName());
|
||||
env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
|
||||
env.setProperty(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
|
||||
env.setProperty(Context.SECURITY_CREDENTIALS, "secret");
|
||||
env.putAll(configuration.toJndiEnvironment());
|
||||
|
||||
try {
|
||||
new InitialDirContext(env);
|
||||
} catch (NamingException e) {
|
||||
logger.error("Failed to start directory service", e);
|
||||
return;
|
||||
}
|
||||
|
||||
running = true;
|
||||
|
||||
try {
|
||||
importLdifs();
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to import LDIF file(s)", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void importLdifs() throws IOException, NamingException {
|
||||
// Import any ldif files
|
||||
Resource[] ldifs = ctxt.getResources(ldifResources);
|
||||
|
||||
// Note that we can't just import using the ServerContext returned
|
||||
// from starting Apace 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) {
|
||||
try {
|
||||
String ldifFile = ldifs[0].getFile().getAbsolutePath();
|
||||
logger.info("Loading LDIF file: " + ldifFile);
|
||||
LdifFileLoader loader = new LdifFileLoader(dirContext, ldifFile);
|
||||
loader.execute();
|
||||
} finally {
|
||||
dirContext.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void stop() {
|
||||
Properties env = new Properties();
|
||||
env.setProperty(Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName());
|
||||
env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
|
||||
env.setProperty(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
|
||||
env.setProperty(Context.SECURITY_CREDENTIALS, "secret");
|
||||
|
||||
ShutdownConfiguration shutdown = new ShutdownConfiguration(configuration.getInstanceId());
|
||||
env.putAll(shutdown.toJndiEnvironment());
|
||||
|
||||
logger.info("Shutting down directory server with Id '" + configuration.getInstanceId() + "'");
|
||||
|
||||
try {
|
||||
new InitialContext(env);
|
||||
} catch (NamingException e) {
|
||||
logger.error("Failed to shutdown directory server", e);
|
||||
return;
|
||||
}
|
||||
|
||||
running = false;
|
||||
|
||||
if (workingDir.exists()) {
|
||||
logger.info("Deleting working directory " + workingDir.getAbsolutePath());
|
||||
deleteDir(workingDir);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
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>.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class DefaultLdapUsernameToDnMapper implements LdapUsernameToDnMapper {
|
||||
private String userDnBase;
|
||||
private 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
return dn;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.ldap.core.support.DirContextAuthenticationStrategy;
|
||||
import org.springframework.ldap.core.support.LdapContextSource;
|
||||
import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy;
|
||||
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.
|
||||
* <p>
|
||||
* From Spring Security 2.5, 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.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DefaultSpringSecurityContextSource.class);
|
||||
private String rootDn;
|
||||
|
||||
/**
|
||||
* Create and initialize an instance which will connect to the supplied LDAP URL.
|
||||
*
|
||||
* @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);
|
||||
|
||||
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);
|
||||
|
||||
urls.add(url.substring(0, url.lastIndexOf(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");
|
||||
}
|
||||
}
|
||||
|
||||
setUrls(urls.toArray(new String[urls.size()]));
|
||||
setBase(rootDn);
|
||||
setPooled(true);
|
||||
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.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.
|
||||
* </p>
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public interface LdapAuthoritiesPopulator {
|
||||
//~ 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.
|
||||
*
|
||||
*/
|
||||
List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.security.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.
|
||||
* </p>
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public interface LdapUserSearch {
|
||||
//~ 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;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
|
||||
/**
|
||||
* Constructs an Ldap Distinguished Name from a username.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public interface LdapUsernameToDnMapper {
|
||||
DistinguishedName buildDn(String username);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
|
||||
/**
|
||||
* LDAP Utility methods.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public final class LdapUtils {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(LdapUtils.class);
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
private LdapUtils() {
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
if (baseDn.length() == 0) {
|
||||
return fullDn;
|
||||
}
|
||||
|
||||
DistinguishedName base = new DistinguishedName(baseDn);
|
||||
DistinguishedName full = new DistinguishedName(fullDn);
|
||||
|
||||
if(base.equals(full)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN");
|
||||
|
||||
full.removeFirst(base);
|
||||
|
||||
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());
|
||||
|
||||
if(dn.contains(baseDn)) {
|
||||
return dn;
|
||||
}
|
||||
|
||||
baseDn.append(dn);
|
||||
|
||||
return baseDn;
|
||||
}
|
||||
|
||||
public static byte[] getUtf8Bytes(String s) {
|
||||
try {
|
||||
return s.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Should be impossible since UTF-8 is required by all implementations
|
||||
throw new IllegalStateException("Failed to convert string to UTF-8 bytes. Shouldn't be possible");
|
||||
}
|
||||
}
|
||||
|
||||
public static String getUtf8BytesAsString(byte[] utf8) {
|
||||
try {
|
||||
return new String(utf8, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Should be impossible since UTF-8 is required by all implementations
|
||||
throw new IllegalStateException("Failed to convert string to UTF-8 bytes. Shouldn't be possible");
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertPasswordToString(Object passObj) {
|
||||
Assert.notNull(passObj, "Password object to convert must not be null");
|
||||
|
||||
if(passObj instanceof byte[]) {
|
||||
return getUtf8BytesAsString((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);
|
||||
|
||||
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 (urlRootDn.startsWith("/")) {
|
||||
urlRootDn = urlRootDn.substring(1);
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @deprecated Spring ldap is used instead.
|
||||
* @version $Id$
|
||||
*/
|
||||
public interface NamingExceptionTranslator {
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
DataAccessException translate(String task, NamingException e);
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import org.springframework.security.Authentication;
|
||||
import org.springframework.security.context.SecurityContextHolder;
|
||||
import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.userdetails.ldap.LdapUserDetails;
|
||||
import org.springframework.ldap.core.AuthenticationSource;
|
||||
|
||||
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}.
|
||||
* <p>
|
||||
* 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
|
||||
* @version $Id$
|
||||
*/
|
||||
public class SpringSecurityAuthenticationSource implements AuthenticationSource {
|
||||
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();
|
||||
|
||||
if (authentication == null) {
|
||||
log.warn("No Authentication object set in SecurityContext - returning empty String as Principal");
|
||||
return "";
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 "";
|
||||
}
|
||||
|
||||
return (String) authentication.getCredentials();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
|
||||
import javax.naming.directory.DirContext;
|
||||
|
||||
/**
|
||||
* Extension of {@link ContextSource} which allows binding explicitly as a particular user.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
*
|
||||
* @deprecated As of Spring LDAP 1.3, ContextSource provides this method itself.
|
||||
*/
|
||||
public interface SpringSecurityContextSource extends BaseLdapPathContextSource {
|
||||
|
||||
/**
|
||||
* Obtains a context using the supplied distinguished name and credentials.
|
||||
*
|
||||
* @param userDn the distinguished name of the user to authenticate as
|
||||
* @param credentials the user's password
|
||||
* @return a context authenticated as the supplied user
|
||||
*/
|
||||
DirContext getReadWriteContext(String userDn, Object credentials);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.PartialResultException;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.directory.SearchResult;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.ldap.core.ContextExecutor;
|
||||
import org.springframework.ldap.core.ContextMapper;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.ldap.core.LdapEncoder;
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* Extension of Spring LDAP's LdapTemplate class which adds extra functionality required by Spring Security.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Luke Taylor
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SpringSecurityLdapTemplate extends LdapTemplate {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
private static final Log logger = LogFactory.getLog(SpringSecurityLdapTemplate.class);
|
||||
|
||||
public static final String[] NO_ATTRS = new String[0];
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
/** Default search controls */
|
||||
private SearchControls searchControls = new SearchControls();
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
public SpringSecurityLdapTemplate(ContextSource contextSource) {
|
||||
Assert.notNull(contextSource, "ContextSource cannot be null");
|
||||
setContextSource(contextSource);
|
||||
|
||||
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
/**
|
||||
* 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})";
|
||||
|
||||
class LdapCompareCallback implements ContextExecutor {
|
||||
|
||||
public Object executeWithContext(DirContext ctx) throws NamingException {
|
||||
SearchControls ctls = new SearchControls();
|
||||
ctls.setReturningAttributes(NO_ATTRS);
|
||||
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
||||
|
||||
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, new Object[] {value}, ctls);
|
||||
|
||||
return Boolean.valueOf(results.hasMore());
|
||||
}
|
||||
}
|
||||
|
||||
Boolean matches = (Boolean) executeReadOnly(new LdapCompareCallback());
|
||||
|
||||
return matches.booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (DirContextOperations) executeReadOnly(new ContextExecutor() {
|
||||
public Object executeWithContext(DirContext ctx) throws NamingException {
|
||||
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
|
||||
|
||||
// Object object = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
|
||||
|
||||
return new DirContextAdapter(attrs, new DistinguishedName(dn),
|
||||
new DistinguishedName(ctx.getNameInNamespace()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// Escape the params acording to RFC2254
|
||||
Object[] encodedParams = new String[params.length];
|
||||
|
||||
for (int i=0; i < params.length; i++) {
|
||||
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
|
||||
}
|
||||
|
||||
String formattedFilter = MessageFormat.format(filter, encodedParams);
|
||||
logger.debug("Using filter: " + formattedFilter);
|
||||
|
||||
final HashSet<String> set = new HashSet<String>();
|
||||
|
||||
ContextMapper roleMapper = new ContextMapper() {
|
||||
public Object mapFromContext(Object ctx) {
|
||||
DirContextAdapter adapter = (DirContextAdapter) ctx;
|
||||
String[] values = adapter.getStringAttributes(attributeName);
|
||||
if (values == null || values.length == 0) {
|
||||
logger.debug("No attribute value found for '" + attributeName + "'");
|
||||
} else {
|
||||
set.addAll(Arrays.asList(values));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
SearchControls ctls = new SearchControls();
|
||||
ctls.setSearchScope(searchControls.getSearchScope());
|
||||
ctls.setReturningAttributes(new String[] {attributeName});
|
||||
|
||||
search(base, formattedFilter, ctls, roleMapper);
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
return (DirContextOperations) executeReadOnly(new ContextExecutor() {
|
||||
public Object executeWithContext(DirContext ctx) throws NamingException {
|
||||
DistinguishedName ctxBaseDn = new DistinguishedName(ctx.getNameInNamespace());
|
||||
NamingEnumeration<SearchResult> resultsEnum = ctx.search(base, filter, params, searchControls);
|
||||
Set<DirContextOperations> results = new HashSet<DirContextOperations>();
|
||||
try {
|
||||
while (resultsEnum.hasMore()) {
|
||||
|
||||
SearchResult searchResult = resultsEnum.next();
|
||||
// Work out the DN of the matched entry
|
||||
StringBuffer dn = new StringBuffer(searchResult.getName());
|
||||
|
||||
if (base.length() > 0) {
|
||||
dn.append(",");
|
||||
dn.append(base);
|
||||
}
|
||||
|
||||
results.add(new DirContextAdapter(searchResult.getAttributes(),
|
||||
new DistinguishedName(dn.toString()), ctxBaseDn));
|
||||
}
|
||||
} catch (PartialResultException e) {
|
||||
logger.info("Ignoring PartialResultException");
|
||||
}
|
||||
|
||||
if (results.size() == 0) {
|
||||
throw new IncorrectResultSizeDataAccessException(1, 0);
|
||||
}
|
||||
|
||||
if (results.size() > 1) {
|
||||
throw new IncorrectResultSizeDataAccessException(1, results.size());
|
||||
}
|
||||
|
||||
return results.toArray()[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap.populator;
|
||||
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.GrantedAuthorityImpl;
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import javax.naming.directory.SearchControls;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
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:
|
||||
* <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
|
||||
* cn: developers
|
||||
* description: Spring Security Developers
|
||||
* member: uid=ben,ou=people,dc=springframework,dc=org
|
||||
* member: uid=luke,ou=people,dc=springframework,dc=org
|
||||
* 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>InitialDirContextFactory</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.
|
||||
* <pre>
|
||||
* <bean id="ldapAuthoritiesPopulator"
|
||||
* class="org.springframework.security.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
|
||||
* <constructor-arg ref="contextSource"/>
|
||||
* <constructor-arg value="ou=groups"/>
|
||||
* <property name="groupRoleAttribute" value="ou"/>
|
||||
* <!-- the following properties are shown with their default values -->
|
||||
* <property name="searchSubTree" value="false"/>
|
||||
* <property name="rolePrefix" value="ROLE_"/>
|
||||
* <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".
|
||||
* <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>.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DefaultLdapAuthoritiesPopulator.class);
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
/**
|
||||
* A default role which will be assigned to all authenticated users if set
|
||||
*/
|
||||
private GrantedAuthority defaultRole;
|
||||
|
||||
private 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 SearchControls searchControls = new SearchControls();
|
||||
|
||||
/**
|
||||
* 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 pattern to be used for the user search. {0} is the user's DN
|
||||
*/
|
||||
private String groupSearchFilter = "(member={0})";
|
||||
|
||||
/**
|
||||
* Attributes of the User's LDAP Object that contain role name information.
|
||||
*/
|
||||
|
||||
// private String[] userRoleAttributes = null;
|
||||
private String rolePrefix = "ROLE_";
|
||||
private boolean convertToUpperCase = true;
|
||||
|
||||
//~ 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.
|
||||
*/
|
||||
public DefaultLdapAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
|
||||
Assert.notNull(contextSource, "contextSource must not be null");
|
||||
ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
|
||||
ldapTemplate.setSearchControls(searchControls);
|
||||
setGroupSearchBase(groupSearchBase);
|
||||
}
|
||||
|
||||
//~ 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
|
||||
*/
|
||||
|
||||
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 List<GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username) {
|
||||
String userDn = user.getNameInNamespace();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Getting authorities for user " + userDn);
|
||||
}
|
||||
|
||||
Set<GrantedAuthority> roles = getGroupMembershipRoles(userDn, username);
|
||||
|
||||
Set<GrantedAuthority> extraRoles = getAdditionalRoles(user, username);
|
||||
|
||||
if (extraRoles != null) {
|
||||
roles.addAll(extraRoles);
|
||||
}
|
||||
|
||||
if (defaultRole != null) {
|
||||
roles.add(defaultRole);
|
||||
}
|
||||
|
||||
List<GrantedAuthority> result = new ArrayList<GrantedAuthority>(roles.size());
|
||||
result.addAll(roles);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
|
||||
if (getGroupSearchBase() == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
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() + "'");
|
||||
}
|
||||
|
||||
Set<String> userRoles = ldapTemplate.searchForSingleAttributeValues(getGroupSearchBase(), groupSearchFilter,
|
||||
new String[]{userDn, username}, groupRoleAttribute);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Roles from search: " + userRoles);
|
||||
}
|
||||
|
||||
for (String role : userRoles) {
|
||||
|
||||
if (convertToUpperCase) {
|
||||
role = role.toUpperCase();
|
||||
}
|
||||
|
||||
authorities.add(new GrantedAuthorityImpl(rolePrefix + role));
|
||||
}
|
||||
|
||||
return authorities;
|
||||
}
|
||||
|
||||
protected ContextSource getContextSource() {
|
||||
return ldapTemplate.getContextSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the group search base (name to search under)
|
||||
*
|
||||
* @param groupSearchBase if this is an empty string the search will be performed from the root DN of the context
|
||||
* factory.
|
||||
*/
|
||||
private void setGroupSearchBase(String groupSearchBase) {
|
||||
Assert.notNull(groupSearchBase, "The groupSearchBase (name to search under), must not be null.");
|
||||
this.groupSearchBase = groupSearchBase;
|
||||
if (groupSearchBase.length() == 0) {
|
||||
logger.info("groupSearchBase is empty. Searches will be performed from the context source base");
|
||||
}
|
||||
}
|
||||
|
||||
protected String getGroupSearchBase() {
|
||||
return groupSearchBase;
|
||||
}
|
||||
|
||||
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 GrantedAuthorityImpl(defaultRole);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package org.springframework.security.ldap.populator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.userdetails.UserDetailsService;
|
||||
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.
|
||||
*
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
*/
|
||||
public class UserDetailsServiceLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
public UserDetailsServiceLdapAuthoritiesPopulator(UserDetailsService userService) {
|
||||
Assert.notNull(userService, "userDetailsService cannot be null");
|
||||
this.userDetailsService = userService;
|
||||
}
|
||||
|
||||
public List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
|
||||
return userDetailsService.loadUserByUsername(username).getAuthorities();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
LdapAuthoritiesPopulator implementations.
|
||||
</body>
|
||||
</html>
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap.search;
|
||||
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
import org.springframework.security.ldap.LdapUserSearch;
|
||||
|
||||
import org.springframework.security.userdetails.UsernameNotFoundException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||
|
||||
import javax.naming.directory.SearchControls;
|
||||
|
||||
|
||||
/**
|
||||
* LdapUserSearch implementation which uses an Ldap filter to locate the user.
|
||||
*
|
||||
* @author Robert Sanders
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*
|
||||
* @see SearchControls
|
||||
*/
|
||||
public class FilterBasedLdapUserSearch implements LdapUserSearch {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(FilterBasedLdapUserSearch.class);
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private 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 SearchControls searchControls = new SearchControls();
|
||||
|
||||
/** 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 String searchFilter;
|
||||
|
||||
//~ 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).");
|
||||
|
||||
this.searchFilter = searchFilter;
|
||||
this.contextSource = contextSource;
|
||||
this.searchBase = searchBase;
|
||||
|
||||
setSearchSubtree(true);
|
||||
|
||||
if (searchBase.length() == 0) {
|
||||
logger.info("SearchBase not set. Searches will be performed from the root: "
|
||||
+ contextSource.getBaseLdapPath());
|
||||
}
|
||||
}
|
||||
|
||||
//~ 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);
|
||||
}
|
||||
|
||||
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource);
|
||||
|
||||
template.setSearchControls(searchControls);
|
||||
|
||||
try {
|
||||
|
||||
return template.searchForSingleEntry(searchBase, searchFilter, new String[] {username});
|
||||
|
||||
} catch (IncorrectResultSizeDataAccessException notFound) {
|
||||
if (notFound.getActualSize() == 0) {
|
||||
throw new UsernameNotFoundException("User " + username + " not found in directory.", username);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
<tt>LdapUserSearch</tt> implementations. These may be used to locate the user in the directory.
|
||||
</body>
|
||||
</html>
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.Authentication;
|
||||
import org.springframework.security.AuthenticationException;
|
||||
import org.springframework.security.AuthenticationServiceException;
|
||||
import org.springframework.security.BadCredentialsException;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.SpringSecurityMessageSource;
|
||||
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
|
||||
import org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator;
|
||||
import org.springframework.security.providers.AuthenticationProvider;
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.userdetails.ldap.LdapUserDetailsMapper;
|
||||
import org.springframework.security.userdetails.ldap.UserDetailsContextMapper;
|
||||
import org.springframework.security.util.AuthorityUtils;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.ldap.NamingException;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.providers.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 responsibilites 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.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates
|
||||
* the user by "binding" as that user, and
|
||||
* {@link org.springframework.security.providers.ldap.authenticator.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).
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* <p>
|
||||
* 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">
|
||||
* <constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/>
|
||||
* <property name="userDn" value="cn=manager,dc=springframework,dc=org"/>
|
||||
* <property name="password" value="password"/>
|
||||
* </bean>
|
||||
*
|
||||
* <bean id="ldapAuthProvider"
|
||||
* class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
|
||||
* <constructor-arg>
|
||||
* <bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
|
||||
* <constructor-arg ref="contextSource"/>
|
||||
* <property name="userDnPatterns"><list><value>uid={0},ou=people</value></list></property>
|
||||
* </bean>
|
||||
* </constructor-arg>
|
||||
* <constructor-arg>
|
||||
* <bean class="org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator">
|
||||
* <constructor-arg ref="contextSource"/>
|
||||
* <constructor-arg value="ou=groups"/>
|
||||
* <property name="groupRoleAttribute" value="ou"/>
|
||||
* </bean>
|
||||
* </constructor-arg>
|
||||
* </bean>
|
||||
*</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.
|
||||
* <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">
|
||||
* draft-ietf-ldapbis-authmeth-19.txt</a>.
|
||||
*
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*
|
||||
* @see org.springframework.security.providers.ldap.authenticator.BindAuthenticator
|
||||
* @see DefaultLdapAuthoritiesPopulator
|
||||
*/
|
||||
public class LdapAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(LdapAuthenticationProvider.class);
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
|
||||
|
||||
private LdapAuthenticator authenticator;
|
||||
private LdapAuthoritiesPopulator authoritiesPopulator;
|
||||
private UserDetailsContextMapper userDetailsContextMapper = new LdapUserDetailsMapper();
|
||||
private boolean useAuthenticationRequestCredentials = true;
|
||||
private boolean hideUserNotFoundExceptions = true;
|
||||
|
||||
//~ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 NullAuthoritiesPopulator());
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
private void setAuthenticator(LdapAuthenticator authenticator) {
|
||||
Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
private LdapAuthenticator getAuthenticator() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
private void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) {
|
||||
Assert.notNull(authoritiesPopulator, "An LdapAuthoritiesPopulator must be supplied");
|
||||
this.authoritiesPopulator = authoritiesPopulator;
|
||||
}
|
||||
|
||||
protected LdapAuthoritiesPopulator getAuthoritiesPopulator() {
|
||||
return authoritiesPopulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(UsernamePasswordAuthenticationToken, 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 <tt>UserDetailsContextMapper</tt> strategy for use by subclasses.
|
||||
*/
|
||||
protected UserDetailsContextMapper getUserDetailsContextMapper() {
|
||||
return userDetailsContextMapper;
|
||||
}
|
||||
|
||||
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
|
||||
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <tt>UserDetailsContextMapper</tt>.
|
||||
* 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 Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
|
||||
messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
|
||||
"Only UsernamePasswordAuthenticationToken is supported"));
|
||||
|
||||
UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken)authentication;
|
||||
|
||||
String username = userToken.getName();
|
||||
|
||||
if (!StringUtils.hasLength(username)) {
|
||||
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
|
||||
"Empty Username"));
|
||||
}
|
||||
|
||||
String password = (String) authentication.getCredentials();
|
||||
Assert.notNull(password, "Null password was supplied in authentication token");
|
||||
|
||||
if (password.length() == 0) {
|
||||
logger.debug("Rejecting empty password for user " + username);
|
||||
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyPassword",
|
||||
"Empty Password"));
|
||||
}
|
||||
|
||||
try {
|
||||
DirContextOperations userData = getAuthenticator().authenticate(authentication);
|
||||
|
||||
List<GrantedAuthority> extraAuthorities = loadUserAuthorities(userData, username, password);
|
||||
|
||||
UserDetails user = userDetailsContextMapper.mapUserFromContext(userData, username, extraAuthorities);
|
||||
|
||||
return createSuccessfulAuthentication(userToken, user);
|
||||
} catch (UsernameNotFoundException notFound) {
|
||||
if (hideUserNotFoundExceptions) {
|
||||
throw new BadCredentialsException(messages.getMessage(
|
||||
"LdapAuthenticationProvider.badCredentials", "Bad credentials"));
|
||||
} else {
|
||||
throw notFound;
|
||||
}
|
||||
} catch (NamingException ldapAccessFailure) {
|
||||
throw new AuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) {
|
||||
return getAuthoritiesPopulator().getGrantedAuthorities(userData, username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the final <tt>Authentication</tt> object which will be returned from the <tt>authenticate</tt> 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();
|
||||
|
||||
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
|
||||
}
|
||||
|
||||
public boolean supports(Class<? extends Object> authentication) {
|
||||
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
|
||||
//~ Inner Classes ==================================================================================================
|
||||
|
||||
private static class NullAuthoritiesPopulator implements LdapAuthoritiesPopulator {
|
||||
public List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userDetails, String username) {
|
||||
return AuthorityUtils.NO_AUTHORITIES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap;
|
||||
|
||||
import org.springframework.security.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.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*
|
||||
* @see org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator
|
||||
* @see org.springframework.security.ldap.populator.UserDetailsServiceLdapAuthoritiesPopulator
|
||||
*/
|
||||
public interface LdapAuthenticator {
|
||||
//~ 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);
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap.authenticator;
|
||||
|
||||
import org.springframework.security.SpringSecurityMessageSource;
|
||||
import org.springframework.security.ldap.LdapUserSearch;
|
||||
import org.springframework.security.providers.ldap.LdapAuthenticator;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for the authenticator implementations.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, InitializingBean, MessageSourceAware {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private 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();
|
||||
|
||||
/** 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;
|
||||
|
||||
//~ 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;
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
List<String> userDns = new ArrayList<String>(userDnFormat.length);
|
||||
String[] args = new String[] {username};
|
||||
|
||||
synchronized (userDnFormat) {
|
||||
for (int i = 0; i < userDnFormat.length; i++) {
|
||||
userDns.add(userDnFormat[i].format(args));
|
||||
}
|
||||
}
|
||||
|
||||
return userDns;
|
||||
}
|
||||
|
||||
protected LdapUserSearch getUserSearch() {
|
||||
return userSearch;
|
||||
}
|
||||
|
||||
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 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]);
|
||||
}
|
||||
}
|
||||
|
||||
public void setUserSearch(LdapUserSearch userSearch) {
|
||||
Assert.notNull(userSearch, "The userSearch cannot be set to null");
|
||||
this.userSearch = userSearch;
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap.authenticator;
|
||||
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.DirContext;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.ldap.NamingException;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||
import org.springframework.ldap.support.LdapUtils;
|
||||
import org.springframework.security.Authentication;
|
||||
import org.springframework.security.BadCredentialsException;
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* An authenticator which binds as a user.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*
|
||||
* @see AbstractLdapAuthenticator
|
||||
*/
|
||||
public class BindAuthenticator extends AbstractLdapAuthenticator {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(BindAuthenticator.class);
|
||||
|
||||
//~ 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);
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
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();
|
||||
|
||||
// If DN patterns are configured, try authenticating with them directly
|
||||
for (String dn : getUserDns(username)) {
|
||||
user = bindWithDn(dn, 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"));
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private DirContextOperations bindWithDn(String userDn, String username, String password) {
|
||||
BaseLdapPathContextSource ctxSource = (BaseLdapPathContextSource) getContextSource();
|
||||
DistinguishedName fullDn = new DistinguishedName(userDn);
|
||||
fullDn.prepend(ctxSource.getBaseLdapPath());
|
||||
|
||||
logger.debug("Attempting to bind as " + fullDn);
|
||||
|
||||
try {
|
||||
DirContext ctx = getContextSource().getContext(fullDn.toString(), password);
|
||||
Attributes attrs = ctx.getAttributes(userDn, getUserAttributes());
|
||||
|
||||
return new DirContextAdapter(attrs, new DistinguishedName(userDn), ctxSource.getBaseLdapPath());
|
||||
} 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(userDn, username, e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} catch (javax.naming.NamingException e) {
|
||||
throw LdapUtils.convertLdapException(e);
|
||||
}
|
||||
|
||||
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 log.
|
||||
*/
|
||||
protected void handleBindException(String userDn, String username, Throwable cause) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to bind as " + userDn + ": " + cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap.authenticator;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.ldap.NameNotFoundException;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||
import org.springframework.security.Authentication;
|
||||
import org.springframework.security.BadCredentialsException;
|
||||
import org.springframework.security.ldap.LdapUtils;
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.providers.encoding.LdapShaPasswordEncoder;
|
||||
import org.springframework.security.providers.encoding.PasswordEncoder;
|
||||
import org.springframework.security.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.providers.ldap.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}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public final class PasswordComparisonAuthenticator extends AbstractLdapAuthenticator {
|
||||
//~ Static fields/initializers =====================================================================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(PasswordComparisonAuthenticator.class);
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private PasswordEncoder passwordEncoder = new LdapShaPasswordEncoder();
|
||||
private String passwordAttributeName = "userPassword";
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
public PasswordComparisonAuthenticator(BaseLdapPathContextSource contextSource) {
|
||||
super(contextSource);
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
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();
|
||||
|
||||
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getContextSource());
|
||||
|
||||
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) {
|
||||
throw new UsernameNotFoundException("User not found: " + username, username);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Performing LDAP compare of password attribute '" + passwordAttributeName + "' for user '" +
|
||||
user.getDn() +"'");
|
||||
}
|
||||
|
||||
String encodedPassword = passwordEncoder.encodePassword(password, null);
|
||||
byte[] passwordBytes = LdapUtils.getUtf8Bytes(encodedPassword);
|
||||
|
||||
if (!ldapTemplate.compare(user.getDn().toString(), passwordAttributeName, passwordBytes)) {
|
||||
throw new BadCredentialsException(messages.getMessage("PasswordComparisonAuthenticator.badCredentials",
|
||||
"Bad credentials"));
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setPasswordAttributeName(String passwordAttribute) {
|
||||
Assert.hasLength(passwordAttribute, "passwordAttributeName must not be empty or null");
|
||||
this.passwordAttributeName = passwordAttribute;
|
||||
}
|
||||
|
||||
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
|
||||
Assert.notNull(passwordEncoder, "passwordEncoder must not be null.");
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
LDAP authenticator implementations.
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
The LDAP authentication provider package. Interfaces are provided for
|
||||
both authentication and retrieval of user roles from an LDAP server.
|
||||
</p>
|
||||
<p>
|
||||
The main provider class is <tt>LdapAuthenticationProvider</tt>.
|
||||
This is configured with an <tt>LdapAuthenticator</tt> instance and
|
||||
an <tt>LdapAuthoritiesPopulator</tt>. The latter is used to obtain the
|
||||
list of roles for the user.
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,277 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Luke
|
||||
* @version $Id$
|
||||
*/
|
||||
public class InetOrgPerson extends Person {
|
||||
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 getMail() {
|
||||
return mail;
|
||||
}
|
||||
|
||||
public String getEmployeeNumber() {
|
||||
return employeeNumber;
|
||||
}
|
||||
|
||||
public String getInitials() {
|
||||
return initials;
|
||||
}
|
||||
|
||||
public String getDestinationIndicator() {
|
||||
return destinationIndicator;
|
||||
}
|
||||
|
||||
public String getO() {
|
||||
return o;
|
||||
}
|
||||
|
||||
public String getOu() {
|
||||
return ou;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getCarLicense() {
|
||||
return carLicense;
|
||||
}
|
||||
|
||||
public String getDepartmentNumber() {
|
||||
return departmentNumber;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getHomePhone() {
|
||||
return homePhone;
|
||||
}
|
||||
|
||||
public String getRoomNumber() {
|
||||
return roomNumber;
|
||||
}
|
||||
|
||||
public String getHomePostalAddress() {
|
||||
return homePostalAddress;
|
||||
}
|
||||
|
||||
public String getMobile() {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
public String getPostalAddress() {
|
||||
return postalAddress;
|
||||
}
|
||||
|
||||
public String getPostalCode() {
|
||||
return postalCode;
|
||||
}
|
||||
|
||||
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"});
|
||||
}
|
||||
|
||||
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(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();
|
||||
}
|
||||
|
||||
public void setMail(String email) {
|
||||
((InetOrgPerson) instance).mail = email;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
((InetOrgPerson) instance).uid = uid;
|
||||
|
||||
if(instance.getUsername() == null) {
|
||||
setUsername(uid);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInitials(String initials) {
|
||||
((InetOrgPerson) instance).initials = initials;
|
||||
}
|
||||
|
||||
public void setO(String organization) {
|
||||
((InetOrgPerson) instance).o = organization;
|
||||
}
|
||||
|
||||
public void setOu(String ou) {
|
||||
((InetOrgPerson) instance).ou = ou;
|
||||
}
|
||||
|
||||
public void setRoomNumber(String no) {
|
||||
((InetOrgPerson) instance).roomNumber = no;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
((InetOrgPerson) instance).title = title;
|
||||
}
|
||||
|
||||
public void setCarLicense(String carLicense) {
|
||||
((InetOrgPerson) instance).carLicense = carLicense;
|
||||
}
|
||||
|
||||
public void setDepartmentNumber(String departmentNumber) {
|
||||
((InetOrgPerson) instance).departmentNumber = departmentNumber;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
((InetOrgPerson) instance).displayName = displayName;
|
||||
}
|
||||
|
||||
public void setEmployeeNumber(String no) {
|
||||
((InetOrgPerson) instance).employeeNumber = no;
|
||||
}
|
||||
|
||||
public void setDestinationIndicator(String destination) {
|
||||
((InetOrgPerson) instance).destinationIndicator = destination;
|
||||
}
|
||||
|
||||
public void setHomePhone(String homePhone) {
|
||||
((InetOrgPerson) instance).homePhone = homePhone;
|
||||
}
|
||||
|
||||
public void setStreet(String street) {
|
||||
((InetOrgPerson) instance).street = street;
|
||||
}
|
||||
|
||||
public void setPostalCode(String postalCode) {
|
||||
((InetOrgPerson) instance).postalCode = postalCode;
|
||||
}
|
||||
|
||||
public void setPostalAddress(String postalAddress) {
|
||||
((InetOrgPerson) instance).postalAddress = postalAddress;
|
||||
}
|
||||
|
||||
public void setMobile(String mobile) {
|
||||
((InetOrgPerson) instance).mobile = mobile;
|
||||
}
|
||||
|
||||
public void setHomePostalAddress(String homePostalAddress) {
|
||||
((InetOrgPerson) instance).homePostalAddress = homePostalAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class InetOrgPersonContextMapper implements UserDetailsContextMapper {
|
||||
|
||||
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, List<GrantedAuthority> authorities) {
|
||||
InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx);
|
||||
|
||||
p.setUsername(username);
|
||||
p.setAuthorities(authorities);
|
||||
|
||||
return p.createUserDetails();
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* Captures the information for a user's LDAP entry.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public interface LdapUserDetails extends UserDetails {
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
/**
|
||||
* The DN of the entry for this user's account.
|
||||
*
|
||||
* @return the user's DN
|
||||
*/
|
||||
String getDn();
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.Name;
|
||||
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.util.AuthorityUtils;
|
||||
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.
|
||||
* <p>
|
||||
* An instance may be created as the result of a search, or when user information is retrieved during authentication.
|
||||
* </p>
|
||||
* <p>
|
||||
* An instance of this class will be used by the <tt>LdapAuthenticationProvider</tt> to construct the final user details
|
||||
* object that it returns.
|
||||
* </p>
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapUserDetailsImpl implements LdapUserDetails {
|
||||
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private String dn;
|
||||
private String password;
|
||||
private String username;
|
||||
private List<GrantedAuthority> authorities = AuthorityUtils.NO_AUTHORITIES;
|
||||
private boolean accountNonExpired = true;
|
||||
private boolean accountNonLocked = true;
|
||||
private boolean credentialsNonExpired = true;
|
||||
private boolean enabled = true;
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
protected LdapUserDetailsImpl() {}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
public List<GrantedAuthority> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public String getDn() {
|
||||
return dn;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public boolean isAccountNonExpired() {
|
||||
return accountNonExpired;
|
||||
}
|
||||
|
||||
public boolean isAccountNonLocked() {
|
||||
return accountNonLocked;
|
||||
}
|
||||
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return credentialsNonExpired;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(super.toString()).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) {
|
||||
sb.append("Granted Authorities: ");
|
||||
|
||||
for (int i = 0; i < this.getAuthorities().size(); i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
|
||||
sb.append(this.getAuthorities().get(i).toString());
|
||||
}
|
||||
} else {
|
||||
sb.append("Not granted any authorities");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
//~ 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>();
|
||||
|
||||
public Essence() { }
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
instance.authorities = getGrantedAuthorities();
|
||||
|
||||
LdapUserDetails newInstance = instance;
|
||||
|
||||
instance = null;
|
||||
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
public List<GrantedAuthority> getGrantedAuthorities() {
|
||||
return mutableAuthorities;
|
||||
}
|
||||
|
||||
public void setAccountNonExpired(boolean accountNonExpired) {
|
||||
instance.accountNonExpired = accountNonExpired;
|
||||
}
|
||||
|
||||
public void setAccountNonLocked(boolean accountNonLocked) {
|
||||
instance.accountNonLocked = accountNonLocked;
|
||||
}
|
||||
|
||||
public void setAuthorities(List<GrantedAuthority> authorities) {
|
||||
mutableAuthorities = authorities;
|
||||
}
|
||||
|
||||
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
|
||||
instance.credentialsNonExpired = credentialsNonExpired;
|
||||
}
|
||||
|
||||
public void setDn(String dn) {
|
||||
instance.dn = dn;
|
||||
}
|
||||
|
||||
public void setDn(Name dn) {
|
||||
instance.dn = dn.toString();
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
instance.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
instance.password = password;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
instance.username = username;
|
||||
}
|
||||
}
|
||||
}
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import org.springframework.security.Authentication;
|
||||
import org.springframework.security.BadCredentialsException;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.GrantedAuthorityImpl;
|
||||
import org.springframework.security.context.SecurityContextHolder;
|
||||
import org.springframework.security.ldap.LdapUsernameToDnMapper;
|
||||
import org.springframework.security.ldap.LdapUtils;
|
||||
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.userdetails.UserDetailsManager;
|
||||
import org.springframework.security.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.ldap.core.AttributesMapper;
|
||||
import org.springframework.ldap.core.AttributesMapperCallbackHandler;
|
||||
import org.springframework.ldap.core.ContextExecutor;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
import org.springframework.ldap.core.SearchExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NameNotFoundException;
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.BasicAttribute;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.ModificationItem;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.directory.SearchResult;
|
||||
import javax.naming.ldap.LdapContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <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.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 2.0
|
||||
* @version $Id$
|
||||
*/
|
||||
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 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 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 GrantedAuthorityImpl(rolePrefix + role.toUpperCase());
|
||||
}
|
||||
};
|
||||
|
||||
private String[] attributesToRetrieve;
|
||||
|
||||
public LdapUserDetailsManager(ContextSource contextSource) {
|
||||
template = new LdapTemplate(contextSource);
|
||||
}
|
||||
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
|
||||
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();
|
||||
}
|
||||
|
||||
// protected String getRoleFilter(DistinguishedName dn, String username) {
|
||||
// return new EqualsFilter("uniquemember", dn.toString()).encode();
|
||||
// }
|
||||
|
||||
public void createUser(UserDetails user) {
|
||||
DirContextAdapter ctx = new DirContextAdapter();
|
||||
copyToContext(user, ctx);
|
||||
DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
|
||||
// Check for any existing authorities which might be set for this DN
|
||||
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
|
||||
|
||||
if(authorities.size() > 0) {
|
||||
removeAuthorities(dn, authorities);
|
||||
}
|
||||
|
||||
logger.debug("Creating new user '"+ user.getUsername() + "' with DN '" + dn + "'");
|
||||
|
||||
template.bind(dn, ctx, null);
|
||||
|
||||
addAuthorities(dn, user.getAuthorities());
|
||||
}
|
||||
|
||||
public void updateUser(UserDetails user) {
|
||||
// Assert.notNull(attributesToRetrieve, "Configuration must specify a list of attributes in order to use update.");
|
||||
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, List<GrantedAuthority> authorities) {
|
||||
modifyAuthorities(userDn, authorities, DirContext.ADD_ATTRIBUTE);
|
||||
}
|
||||
|
||||
protected void removeAuthorities(DistinguishedName userDn, List<GrantedAuthority> authorities) {
|
||||
modifyAuthorities(userDn, authorities, DirContext.REMOVE_ATTRIBUTE);
|
||||
}
|
||||
|
||||
private void modifyAuthorities(final DistinguishedName userDn, final List<GrantedAuthority> authorities, final int modType) {
|
||||
template.executeReadWrite(new ContextExecutor() {
|
||||
public Object executeWithContext(DirContext ctx) throws NamingException {
|
||||
for(int i=0; i < authorities.size(); i++) {
|
||||
GrantedAuthority authority = authorities.get(i);
|
||||
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;
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.GrantedAuthorityImpl;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
|
||||
|
||||
/**
|
||||
* The context mapper used by the LDAP authentication provider to create an LDAP user object.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapUserDetailsMapper implements UserDetailsContextMapper {
|
||||
//~ 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;
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, List<GrantedAuthority> authorities) {
|
||||
String dn = ctx.getNameInNamespace();
|
||||
|
||||
logger.debug("Mapping user details from context with DN: " + dn);
|
||||
|
||||
LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence();
|
||||
essence.setDn(dn);
|
||||
|
||||
Object passwordValue = ctx.getObjectAttribute(passwordAttributeName);
|
||||
|
||||
if (passwordValue != null) {
|
||||
essence.setPassword(mapPassword(passwordValue));
|
||||
}
|
||||
|
||||
essence.setUsername(username);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
for (int j = 0; j < rolesForAttribute.length; j++) {
|
||||
GrantedAuthority authority = createAuthority(rolesForAttribute[j]);
|
||||
|
||||
if (authority != null) {
|
||||
essence.addAuthority(authority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the supplied authorities
|
||||
|
||||
for (int i=0; i < authorities.size(); i++) {
|
||||
essence.addAuthority(authorities.get(i));
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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 GrantedAuthorityImpl(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 prefix that should be applied to the role names
|
||||
* @param rolePrefix the prefix (defaults to "ROLE_").
|
||||
*/
|
||||
public void setRolePrefix(String rolePrefix) {
|
||||
this.rolePrefix = rolePrefix;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
|
||||
import org.springframework.security.ldap.LdapUserSearch;
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.userdetails.UserDetailsService;
|
||||
import org.springframework.security.userdetails.UsernameNotFoundException;
|
||||
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>.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapUserDetailsService implements UserDetailsService {
|
||||
private LdapUserSearch userSearch;
|
||||
private LdapAuthoritiesPopulator authoritiesPopulator;
|
||||
private UserDetailsContextMapper userDetailsMapper = new LdapUserDetailsMapper();
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
|
||||
import org.springframework.security.ldap.LdapUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* UserDetails implementation whose properties are based on the LDAP schema for <tt>Person</tt>.
|
||||
*
|
||||
* @author Luke
|
||||
* @since 2.0
|
||||
* @version $Id$
|
||||
*/
|
||||
public class Person extends LdapUserDetailsImpl {
|
||||
private String sn;
|
||||
private String description;
|
||||
private String telephoneNumber;
|
||||
private List<String> cn = new ArrayList<String>();
|
||||
|
||||
protected Person() {
|
||||
}
|
||||
|
||||
public String getSn() {
|
||||
return sn;
|
||||
}
|
||||
|
||||
public String[] getCn() {
|
||||
return cn.toArray(new String[cn.size()]);
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getTelephoneNumber() {
|
||||
return telephoneNumber;
|
||||
}
|
||||
|
||||
protected void populateContext(DirContextAdapter adapter) {
|
||||
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"});
|
||||
}
|
||||
|
||||
public static class Essence extends LdapUserDetailsImpl.Essence {
|
||||
|
||||
public Essence() {
|
||||
}
|
||||
|
||||
public Essence(DirContextOperations ctx) {
|
||||
super(ctx);
|
||||
setCn(ctx.getStringAttributes("cn"));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public Essence(Person copyMe) {
|
||||
super(copyMe);
|
||||
setSn(copyMe.sn);
|
||||
setDescription(copyMe.getDescription());
|
||||
setTelephoneNumber(copyMe.getTelephoneNumber());
|
||||
((Person) instance).cn = new ArrayList<String>(copyMe.cn);
|
||||
}
|
||||
|
||||
protected LdapUserDetailsImpl createTarget() {
|
||||
return new Person();
|
||||
}
|
||||
|
||||
public void setSn(String sn) {
|
||||
((Person) instance).sn = sn;
|
||||
}
|
||||
|
||||
public void setCn(String[] cn) {
|
||||
((Person) instance).cn = Arrays.asList(cn);
|
||||
}
|
||||
|
||||
public void addCn(String value) {
|
||||
((Person) instance).cn.add(value);
|
||||
}
|
||||
|
||||
public void setTelephoneNumber(String tel) {
|
||||
((Person) instance).telephoneNumber = tel;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class PersonContextMapper implements UserDetailsContextMapper {
|
||||
|
||||
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, List<GrantedAuthority> authorities) {
|
||||
Person.Essence p = new Person.Essence(ctx);
|
||||
|
||||
p.setUsername(username);
|
||||
p.setAuthorities(authorities);
|
||||
|
||||
return p.createUserDetails();
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
|
||||
/**
|
||||
* Operations to map a UserDetails object to and from a Spring LDAP <tt>DirContextOperations</tt> implementation.
|
||||
* Used by LdapUserDetailsManager when loading and saving/creating user information.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 2.0
|
||||
* @version $Id$
|
||||
*/
|
||||
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 authority the list of authorities which the user should be given.
|
||||
* @return the user object.
|
||||
*/
|
||||
UserDetails mapUserFromContext(DirContextOperations ctx, String username, List<GrantedAuthority> authority);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package org.springframework.security;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.Authentication;
|
||||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.security.config.SecurityConfigurationException;
|
||||
import org.springframework.security.providers.ProviderManager;
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.providers.ldap.LdapAuthenticationProvider;
|
||||
import org.springframework.security.userdetails.ldap.InetOrgPersonContextMapper;
|
||||
import org.springframework.security.userdetails.ldap.LdapUserDetailsImpl;
|
||||
import org.springframework.security.util.FieldUtils;
|
||||
import org.springframework.security.util.InMemoryXmlApplicationContext;
|
||||
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapProviderBeanDefinitionParserTests {
|
||||
InMemoryXmlApplicationContext appCtx;
|
||||
|
||||
@After
|
||||
public void closeAppContext() {
|
||||
if (appCtx != null) {
|
||||
appCtx.close();
|
||||
appCtx = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleProviderAuthenticatesCorrectly() {
|
||||
setContext("<ldap-server /> <ldap-authentication-provider group-search-filter='member={0}' />");
|
||||
|
||||
LdapAuthenticationProvider provider = getProvider();
|
||||
Authentication auth = provider.authenticate(new UsernamePasswordAuthenticationToken("ben", "benspassword"));
|
||||
LdapUserDetailsImpl ben = (LdapUserDetailsImpl) auth.getPrincipal();
|
||||
|
||||
assertEquals(3, ben.getAuthorities().size());
|
||||
}
|
||||
|
||||
@Test(expected = SecurityConfigurationException.class)
|
||||
public void missingServerEltCausesConfigException() {
|
||||
setContext("<ldap-authentication-provider />");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void supportsPasswordComparisonAuthentication() {
|
||||
setContext("<ldap-server /> " +
|
||||
"<ldap-authentication-provider user-dn-pattern='uid={0},ou=people'>" +
|
||||
" <password-compare />" +
|
||||
"</ldap-authentication-provider>");
|
||||
LdapAuthenticationProvider provider = getProvider();
|
||||
provider.authenticate(new UsernamePasswordAuthenticationToken("ben", "benspassword"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void supportsPasswordComparisonAuthenticationWithHashAttribute() {
|
||||
setContext("<ldap-server /> " +
|
||||
"<ldap-authentication-provider user-dn-pattern='uid={0},ou=people'>" +
|
||||
" <password-compare password-attribute='uid' hash='plaintext'/>" +
|
||||
"</ldap-authentication-provider>");
|
||||
LdapAuthenticationProvider provider = getProvider();
|
||||
provider.authenticate(new UsernamePasswordAuthenticationToken("ben", "ben"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsPasswordComparisonAuthenticationWithPasswordEncoder() {
|
||||
setContext("<ldap-server /> " +
|
||||
"<ldap-authentication-provider user-dn-pattern='uid={0},ou=people'>" +
|
||||
" <password-compare password-attribute='uid'>" +
|
||||
" <password-encoder hash='plaintext'/>" +
|
||||
" </password-compare>" +
|
||||
"</ldap-authentication-provider>");
|
||||
LdapAuthenticationProvider provider = getProvider();
|
||||
provider.authenticate(new UsernamePasswordAuthenticationToken("ben", "ben"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsNonStandardServerId() {
|
||||
setContext("<ldap-server id='myServer'/> " +
|
||||
"<ldap-authentication-provider />");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inetOrgContextMapperIsSupported() throws Exception {
|
||||
setContext(
|
||||
"<ldap-server id='someServer' url='ldap://127.0.0.1:343/dc=springframework,dc=org'/>" +
|
||||
"<ldap-authentication-provider user-details-class='inetOrgPerson'/>");
|
||||
LdapAuthenticationProvider provider = getProvider();
|
||||
assertTrue(FieldUtils.getFieldValue(provider, "userDetailsContextMapper") instanceof InetOrgPersonContextMapper);
|
||||
}
|
||||
|
||||
private void setContext(String context) {
|
||||
appCtx = new InMemoryXmlApplicationContext(context);
|
||||
}
|
||||
|
||||
private LdapAuthenticationProvider getProvider() {
|
||||
ProviderManager authManager = (ProviderManager) appCtx.getBean(BeanIds.AUTHENTICATION_MANAGER);
|
||||
|
||||
assertEquals(1, authManager.getProviders().size());
|
||||
|
||||
LdapAuthenticationProvider provider = (LdapAuthenticationProvider) authManager.getProviders().get(0);
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package org.springframework.security;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
|
||||
import org.springframework.security.util.InMemoryXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapServerBeanDefinitionParserTests {
|
||||
InMemoryXmlApplicationContext appCtx;
|
||||
|
||||
@After
|
||||
public void closeAppContext() {
|
||||
if (appCtx != null) {
|
||||
appCtx.close();
|
||||
appCtx = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void embeddedServerCreationContainsExpectedContextSourceAndData() {
|
||||
appCtx = new InMemoryXmlApplicationContext("<ldap-server />");
|
||||
|
||||
DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx.getBean(BeanIds.CONTEXT_SOURCE);
|
||||
|
||||
// Check data is loaded
|
||||
LdapTemplate template = new LdapTemplate(contextSource);
|
||||
template.lookup("uid=ben,ou=people");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useOfUrlAttributeCreatesCorrectContextSource() {
|
||||
// Create second "server" with a url pointing at embedded one
|
||||
appCtx = new InMemoryXmlApplicationContext("<ldap-server port='33388'/>" +
|
||||
"<ldap-server id='blah' url='ldap://127.0.0.1:33388/dc=springframework,dc=org' />");
|
||||
|
||||
// Check the default context source is still there.
|
||||
appCtx.getBean(BeanIds.CONTEXT_SOURCE);
|
||||
|
||||
DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx.getBean("blah");
|
||||
|
||||
// Check data is loaded as before
|
||||
LdapTemplate template = new LdapTemplate(contextSource);
|
||||
template.lookup("uid=ben,ou=people");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadingSpecificLdifFileIsSuccessful() {
|
||||
appCtx = new InMemoryXmlApplicationContext(
|
||||
"<ldap-server ldif='classpath*:test-server2.xldif' root='dc=monkeymachine,dc=co,dc=uk' />");
|
||||
DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx.getBean(BeanIds.CONTEXT_SOURCE);
|
||||
|
||||
LdapTemplate template = new LdapTemplate(contextSource);
|
||||
template.lookup("uid=pg,ou=gorillas");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package org.springframework.security;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.GrantedAuthorityImpl;
|
||||
import org.springframework.security.util.AuthorityUtils;
|
||||
import org.springframework.security.util.InMemoryXmlApplicationContext;
|
||||
import org.springframework.security.userdetails.UserDetailsService;
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.userdetails.ldap.InetOrgPerson;
|
||||
import org.springframework.security.userdetails.ldap.Person;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.After;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapUserServiceBeanDefinitionParserTests {
|
||||
private InMemoryXmlApplicationContext appCtx;
|
||||
|
||||
@After
|
||||
public void closeAppContext() {
|
||||
if (appCtx != null) {
|
||||
appCtx.close();
|
||||
appCtx = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void minimalConfigurationIsParsedOk() throws Exception {
|
||||
setContext("<ldap-user-service user-search-filter='(uid={0})' /><ldap-server url='ldap://127.0.0.1:343/dc=springframework,dc=org' />");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userServiceReturnsExpectedData() throws Exception {
|
||||
setContext("<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' group-search-filter='member={0}' /><ldap-server />");
|
||||
|
||||
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
|
||||
UserDetails ben = uds.loadUserByUsername("ben");
|
||||
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
|
||||
assertEquals(3, authorities.size());
|
||||
assertTrue(authorities.contains("ROLE_DEVELOPERS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentUserSearchBaseWorksAsExpected() throws Exception {
|
||||
setContext("<ldap-user-service id='ldapUDS' " +
|
||||
" user-search-base='ou=otherpeople' " +
|
||||
" user-search-filter='(cn={0})' " +
|
||||
" group-search-filter='member={0}' /><ldap-server />");
|
||||
|
||||
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
|
||||
UserDetails joe = uds.loadUserByUsername("Joe Smeth");
|
||||
|
||||
assertEquals("Joe Smeth", joe.getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rolePrefixIsSupported() throws Exception {
|
||||
setContext(
|
||||
"<ldap-user-service id='ldapUDS' " +
|
||||
" user-search-filter='(uid={0})' " +
|
||||
" group-search-filter='member={0}' role-prefix='PREFIX_'/>" +
|
||||
"<ldap-user-service id='ldapUDSNoPrefix' " +
|
||||
" user-search-filter='(uid={0})' " +
|
||||
" group-search-filter='member={0}' role-prefix='none'/><ldap-server />");
|
||||
|
||||
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
|
||||
UserDetails ben = uds.loadUserByUsername("ben");
|
||||
assertTrue(AuthorityUtils.authorityListToSet(ben.getAuthorities()).contains("PREFIX_DEVELOPERS"));
|
||||
|
||||
uds = (UserDetailsService) appCtx.getBean("ldapUDSNoPrefix");
|
||||
ben = uds.loadUserByUsername("ben");
|
||||
assertTrue(AuthorityUtils.authorityListToSet(ben.getAuthorities()).contains("DEVELOPERS"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void differentGroupRoleAttributeWorksAsExpected() throws Exception {
|
||||
setContext("<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' group-role-attribute='ou' group-search-filter='member={0}' /><ldap-server />");
|
||||
|
||||
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
|
||||
UserDetails ben = uds.loadUserByUsername("ben");
|
||||
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
|
||||
assertEquals(3, authorities.size());
|
||||
assertTrue(authorities.contains(new GrantedAuthorityImpl("ROLE_DEVELOPER")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSupportedByAuthenticationProviderElement() {
|
||||
setContext(
|
||||
"<ldap-server url='ldap://127.0.0.1:343/dc=springframework,dc=org'/>" +
|
||||
"<authentication-provider>" +
|
||||
" <ldap-user-service user-search-filter='(uid={0})' />" +
|
||||
"</authentication-provider>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void personContextMapperIsSupported() {
|
||||
setContext(
|
||||
"<ldap-server />" +
|
||||
"<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' user-details-class='person'/>");
|
||||
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
|
||||
UserDetails ben = uds.loadUserByUsername("ben");
|
||||
assertTrue(ben instanceof Person);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inetOrgContextMapperIsSupported() {
|
||||
setContext(
|
||||
"<ldap-server id='someServer'/>" +
|
||||
"<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' user-details-class='inetOrgPerson'/>");
|
||||
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
|
||||
UserDetails ben = uds.loadUserByUsername("ben");
|
||||
assertTrue(ben instanceof InetOrgPerson);
|
||||
}
|
||||
|
||||
|
||||
private void setContext(String context) {
|
||||
appCtx = new InMemoryXmlApplicationContext(context);
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import javax.naming.Binding;
|
||||
import javax.naming.ContextNotEmptyException;
|
||||
import javax.naming.Name;
|
||||
import javax.naming.NameNotFoundException;
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.DirContext;
|
||||
|
||||
import org.apache.directory.server.core.DirectoryService;
|
||||
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.security.util.InMemoryXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* Based on class borrowed from Spring Ldap project.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public abstract class AbstractLdapIntegrationTests {
|
||||
private static InMemoryXmlApplicationContext appContext;
|
||||
|
||||
protected AbstractLdapIntegrationTests() {
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void loadContext() throws NamingException {
|
||||
shutdownRunningServers();
|
||||
appContext = new InMemoryXmlApplicationContext("<ldap-server port='53389' ldif='classpath:test-server.ldif'/>");
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void closeContext() throws Exception {
|
||||
if(appContext != null) {
|
||||
appContext.close();
|
||||
}
|
||||
shutdownRunningServers();
|
||||
}
|
||||
|
||||
private static void shutdownRunningServers() throws NamingException {
|
||||
DirectoryService ds = DirectoryService.getInstance();
|
||||
|
||||
if (ds.isStarted()) {
|
||||
System.out.println("WARNING: Discovered running DirectoryService with configuration: " + ds.getConfiguration().getStartupConfiguration().toString());
|
||||
System.out.println("Shutting it down...");
|
||||
ds.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void onSetUp() throws Exception {
|
||||
}
|
||||
|
||||
|
||||
@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(ctx, ldifs.getFile().getAbsolutePath());
|
||||
loader.execute();
|
||||
} finally {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
public BaseLdapPathContextSource getContextSource() {
|
||||
return (BaseLdapPathContextSource)appContext.getBean(BeanIds.CONTEXT_SOURCE);
|
||||
}
|
||||
|
||||
|
||||
private void clearSubContexts(DirContext ctx, Name name) throws NamingException {
|
||||
|
||||
NamingEnumeration<Binding> enumeration = null;
|
||||
try {
|
||||
enumeration = ctx.listBindings(name);
|
||||
while (enumeration.hasMore()) {
|
||||
Binding element = (Binding) 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.ldap.core.support.AbstractContextSource;
|
||||
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class DefaultSpringSecurityContextSourceTests {
|
||||
|
||||
@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 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));
|
||||
}
|
||||
|
||||
static class EnvExposingDefaultSpringSecurityContextSource extends DefaultSpringSecurityContextSource {
|
||||
public EnvExposingDefaultSpringSecurityContextSource(String providerUrl) {
|
||||
super(providerUrl);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Hashtable getAuthenticatedEnvForTest(String userDn, String password) {
|
||||
return getAuthenticatedEnv(userDn, password);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.DirContext;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.integration.junit4.JMock;
|
||||
import org.jmock.integration.junit4.JUnit4Mockery;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link LdapUtils}
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
@RunWith(JMock.class)
|
||||
public class LdapUtilsTests {
|
||||
Mockery context = new JUnit4Mockery();
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
@Test
|
||||
public void testCloseContextSwallowsNamingException() throws Exception {
|
||||
final DirContext dirCtx = context.mock(DirContext.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(dirCtx).close(); will(throwException(new NamingException()));
|
||||
}});
|
||||
|
||||
LdapUtils.closeContext(dirCtx);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRelativeNameReturnsEmptyStringForDnEqualToBaseName() throws Exception {
|
||||
final DirContext mockCtx = context.mock(DirContext.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
atLeast(1).of(mockCtx).getNameInNamespace(); will(returnValue("dc=springframework,dc=org"));
|
||||
}});
|
||||
|
||||
assertEquals("", LdapUtils.getRelativeName("dc=springframework,dc=org", mockCtx));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRelativeNameReturnsFullDnWithEmptyBaseName() throws Exception {
|
||||
final DirContext mockCtx = context.mock(DirContext.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
atLeast(1).of(mockCtx).getNameInNamespace(); will(returnValue(""));
|
||||
}});
|
||||
|
||||
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 = context.mock(DirContext.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
atLeast(1).of(mockCtx).getNameInNamespace(); will(returnValue("dc=springsecurity,dc = org"));
|
||||
}});
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import org.springframework.security.context.SecurityContextHolder;
|
||||
import org.springframework.security.providers.TestingAuthenticationToken;
|
||||
import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.userdetails.ldap.LdapUserDetailsImpl;
|
||||
import org.springframework.security.util.AuthorityUtils;
|
||||
import org.springframework.ldap.core.AuthenticationSource;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
|
||||
import org.junit.After;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class SpringSecurityAuthenticationSourceTests {
|
||||
@Before
|
||||
@After
|
||||
public void clearContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void principalAndCredentialsAreEmptyWithNoAuthentication() {
|
||||
AuthenticationSource source = new SpringSecurityAuthenticationSource();
|
||||
assertEquals("", source.getPrincipal());
|
||||
assertEquals("", source.getCredentials());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void principalIsEmptyForAnonymousUser() {
|
||||
AuthenticationSource source = new SpringSecurityAuthenticationSource();
|
||||
|
||||
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"));
|
||||
|
||||
source.getPrincipal();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expectedCredentialsAreReturned() {
|
||||
AuthenticationSource source = new SpringSecurityAuthenticationSource();
|
||||
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(new Object(), "password"));
|
||||
|
||||
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));
|
||||
|
||||
assertEquals("uid=joe,ou=users", source.getPrincipal());
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.SearchControls;
|
||||
import javax.naming.directory.SearchResult;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.ldap.UncategorizedLdapException;
|
||||
import org.springframework.ldap.core.ContextExecutor;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class SpringSecurityLdapTemplateTests extends AbstractLdapIntegrationTests {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private SpringSecurityLdapTemplate template;
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
public void onSetUp() throws Exception {
|
||||
super.onSetUp();
|
||||
|
||||
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", LdapUtils.getUtf8Bytes("bobspassword")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareOfWrongByteValueFails() {
|
||||
assertFalse(template.compare("uid=bob,ou=people", "userPassword", LdapUtils.getUtf8Bytes("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"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoleSearchForMissingAttributeFailsGracefully() {
|
||||
String param = "uid=ben,ou=people,dc=springframework,dc=org";
|
||||
|
||||
Set<String> values = template.searchForSingleAttributeValues("ou=groups", "(member={0})", new String[] {param}, "mail");
|
||||
|
||||
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:53389");
|
||||
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});
|
||||
}
|
||||
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap.populator;
|
||||
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
|
||||
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
|
||||
import org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator;
|
||||
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class DefaultLdapAuthoritiesPopulatorTests extends AbstractLdapIntegrationTests {
|
||||
private DefaultLdapAuthoritiesPopulator populator;
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
public void onSetUp() throws Exception {
|
||||
super.onSetUp();
|
||||
|
||||
populator = new DefaultLdapAuthoritiesPopulator(getContextSource(), "ou=groups");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultRoleIsAssignedWhenSet() {
|
||||
|
||||
populator.setDefaultRole("ROLE_USER");
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("cn=notfound"));
|
||||
|
||||
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "notfound");
|
||||
assertEquals(1, authorities.size());
|
||||
assertEquals("ROLE_USER", authorities.get(0).getAuthority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupSearchReturnsExpectedRoles() {
|
||||
populator.setRolePrefix("ROLE_");
|
||||
populator.setGroupRoleAttribute("ou");
|
||||
populator.setSearchSubtree(true);
|
||||
populator.setSearchSubtree(false);
|
||||
populator.setConvertToUpperCase(true);
|
||||
populator.setGroupSearchFilter("(member={0})");
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
|
||||
|
||||
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "ben");
|
||||
|
||||
assertEquals("Should have 2 roles", 2, authorities.size());
|
||||
|
||||
Set<String> roles = new HashSet<String>();
|
||||
roles.add(authorities.get(0).toString());
|
||||
roles.add(authorities.get(1).toString());
|
||||
assertTrue(roles.contains("ROLE_DEVELOPER"));
|
||||
assertTrue(roles.contains("ROLE_MANAGER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseOfUsernameParameterReturnsExpectedRoles() {
|
||||
populator.setGroupRoleAttribute("ou");
|
||||
populator.setConvertToUpperCase(true);
|
||||
populator.setGroupSearchFilter("(ou={1})");
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
|
||||
|
||||
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "manager");
|
||||
|
||||
assertEquals("Should have 1 role", 1, authorities.size());
|
||||
assertEquals("ROLE_MANAGER", authorities.get(0).getAuthority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubGroupRolesAreNotFoundByDefault() {
|
||||
populator.setGroupRoleAttribute("ou");
|
||||
populator.setConvertToUpperCase(true);
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
|
||||
|
||||
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "manager");
|
||||
|
||||
assertEquals("Should have 2 roles", 2, authorities.size());
|
||||
Set<String> roles = new HashSet<String>(2);
|
||||
roles.add(authorities.get(0).getAuthority());
|
||||
roles.add(authorities.get(1).getAuthority());
|
||||
assertTrue(roles.contains("ROLE_MANAGER"));
|
||||
assertTrue(roles.contains("ROLE_DEVELOPER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubGroupRolesAreFoundWhenSubtreeSearchIsEnabled() {
|
||||
populator.setGroupRoleAttribute("ou");
|
||||
populator.setConvertToUpperCase(true);
|
||||
populator.setSearchSubtree(true);
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
|
||||
|
||||
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "manager");
|
||||
|
||||
assertEquals("Should have 3 roles", 3, authorities.size());
|
||||
Set<String> roles = new HashSet<String>(3);
|
||||
roles.add(authorities.get(0).getAuthority());
|
||||
roles.add(authorities.get(1).getAuthority());
|
||||
roles.add(authorities.get(2).getAuthority());
|
||||
assertTrue(roles.contains("ROLE_MANAGER"));
|
||||
assertTrue(roles.contains("ROLE_DEVELOPER"));
|
||||
assertTrue(roles.contains("ROLE_SUBMANAGER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserDnWithEscapedCharacterParameterReturnsExpectedRoles() {
|
||||
populator.setGroupRoleAttribute("ou");
|
||||
populator.setConvertToUpperCase(true);
|
||||
populator.setGroupSearchFilter("(member={0})");
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("cn=mouse\\, jerry,ou=people,dc=springframework,dc=org"));
|
||||
|
||||
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "notused");
|
||||
|
||||
assertEquals("Should have 1 role", 1, authorities.size());
|
||||
assertEquals("ROLE_MANAGER", authorities.get(0).getAuthority());
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package org.springframework.security.ldap.populator;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.userdetails.UserDetailsService;
|
||||
import org.springframework.security.util.AuthorityUtils;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
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);
|
||||
when(user.getAuthorities()).thenReturn(AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
|
||||
UserDetailsServiceLdapAuthoritiesPopulator populator = new UserDetailsServiceLdapAuthoritiesPopulator(uds);
|
||||
List<GrantedAuthority> auths = populator.getGrantedAuthorities(new DirContextAdapter(), "joe");
|
||||
|
||||
assertEquals(1, auths.size());
|
||||
assertEquals("ROLE_USER", auths.get(0).getAuthority());
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap.search;
|
||||
|
||||
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
|
||||
import org.springframework.security.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for FilterBasedLdapUserSearch.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class FilterBasedLdapUserSearchTests extends AbstractLdapIntegrationTests {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private BaseLdapPathContextSource dirCtxFactory;
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
public void onSetUp() throws Exception {
|
||||
super.onSetUp();
|
||||
dirCtxFactory = getContextSource();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicSearchSucceeds() {
|
||||
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory);
|
||||
locator.setSearchSubtree(false);
|
||||
locator.setSearchTimeLimit(0);
|
||||
locator.setDerefLinkFlag(false);
|
||||
|
||||
DirContextOperations bob = locator.searchForUser("bob");
|
||||
assertEquals("bob", bob.getStringAttribute("uid"));
|
||||
|
||||
assertEquals(new DistinguishedName("uid=bob,ou=people"), bob.getDn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void searchForNameWithCommaSucceeds() {
|
||||
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory);
|
||||
locator.setSearchSubtree(false);
|
||||
|
||||
DirContextOperations jerry = locator.searchForUser("jerry");
|
||||
assertEquals("jerry", jerry.getStringAttribute("uid"));
|
||||
|
||||
assertEquals(new DistinguishedName("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))))", dirCtxFactory);
|
||||
|
||||
// Search for bob, get back ben...
|
||||
DirContextOperations ben = locator.searchForUser("bob");
|
||||
assertEquals("Ben Alex", ben.getStringAttribute("cn"));
|
||||
|
||||
// assertEquals("uid=ben,ou=people,"+ROOT_DN, ben.getDn());
|
||||
}
|
||||
|
||||
@Test(expected=IncorrectResultSizeDataAccessException.class)
|
||||
public void searchFailsOnMultipleMatches() {
|
||||
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(cn=*)", dirCtxFactory);
|
||||
locator.searchForUser("Ignored");
|
||||
}
|
||||
|
||||
@Test(expected=UsernameNotFoundException.class)
|
||||
public void searchForInvalidUserFails() {
|
||||
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory);
|
||||
locator.searchForUser("Joe");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subTreeSearchSucceeds() {
|
||||
// Don't set the searchBase, so search from the root.
|
||||
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("", "(cn={0})", dirCtxFactory);
|
||||
locator.setSearchSubtree(true);
|
||||
|
||||
DirContextOperations ben = locator.searchForUser("Ben Alex");
|
||||
assertEquals("ben", ben.getStringAttribute("uid"));
|
||||
|
||||
assertEquals(new DistinguishedName("uid=ben,ou=people"), ben.getDn());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void searchWithDifferentSearchBaseIsSuccessful() throws Exception {
|
||||
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=otherpeople", "(cn={0})", dirCtxFactory);
|
||||
DirContextOperations joe = locator.searchForUser("Joe Smeth");
|
||||
assertEquals("Joe Smeth", joe.getStringAttribute("cn"));
|
||||
}
|
||||
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.integration.junit4.JUnit4Mockery;
|
||||
import org.junit.Test;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.security.Authentication;
|
||||
import org.springframework.security.BadCredentialsException;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.userdetails.ldap.LdapUserDetailsMapper;
|
||||
import org.springframework.security.util.AuthorityUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link LdapAuthenticationProvider}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapAuthenticationProviderTests {
|
||||
Mockery jmock = new JUnit4Mockery();
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
@Test
|
||||
public void testSupportsUsernamePasswordAuthenticationToken() {
|
||||
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
|
||||
new MockAuthoritiesPopulator());
|
||||
|
||||
assertTrue(ldapProvider.supports(UsernamePasswordAuthenticationToken.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultMapperIsSet() {
|
||||
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
|
||||
new MockAuthoritiesPopulator());
|
||||
|
||||
assertTrue(ldapProvider.getUserDetailsContextMapper() instanceof LdapUserDetailsMapper);
|
||||
}
|
||||
|
||||
@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("", "bobspassword"));
|
||||
fail("Expected BadCredentialsException for null username");
|
||||
} catch (BadCredentialsException expected) {}
|
||||
}
|
||||
|
||||
@Test(expected=BadCredentialsException.class)
|
||||
public void emptyPasswordIsRejected() {
|
||||
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator());
|
||||
ldapProvider.authenticate(new UsernamePasswordAuthenticationToken("jen", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usernameNotFoundExceptionIsHiddenByDefault() {
|
||||
final LdapAuthenticator authenticator = jmock.mock(LdapAuthenticator.class);
|
||||
final UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken("joe", "password");
|
||||
jmock.checking(new Expectations() {{
|
||||
oneOf(authenticator).authenticate(joe); will(throwException(new UsernameNotFoundException("nobody")));
|
||||
}});
|
||||
|
||||
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
|
||||
try {
|
||||
provider.authenticate(joe);
|
||||
fail();
|
||||
} catch (BadCredentialsException expected) {
|
||||
if (expected instanceof UsernameNotFoundException) {
|
||||
fail("Exception should have been hidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected=UsernameNotFoundException.class)
|
||||
public void usernameNotFoundExceptionIsNotHiddenIfConfigured() {
|
||||
final LdapAuthenticator authenticator = jmock.mock(LdapAuthenticator.class);
|
||||
final UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken("joe", "password");
|
||||
jmock.checking(new Expectations() {{
|
||||
oneOf(authenticator).authenticate(joe); will(throwException(new UsernameNotFoundException("nobody")));
|
||||
}});
|
||||
|
||||
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);
|
||||
|
||||
assertNotNull(ldapProvider.getAuthoritiesPopulator());
|
||||
|
||||
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword");
|
||||
Authentication authResult = ldapProvider.authenticate(authRequest);
|
||||
assertEquals("benspassword", authResult.getCredentials());
|
||||
UserDetails user = (UserDetails) authResult.getPrincipal();
|
||||
assertEquals(2, user.getAuthorities().size());
|
||||
assertEquals("{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=", user.getPassword());
|
||||
assertEquals("ben", user.getUsername());
|
||||
assertEquals("ben", populator.getRequestedUsername());
|
||||
|
||||
ArrayList<String> authorities = new ArrayList<String>();
|
||||
authorities.add(user.getAuthorities().get(0).getAuthority());
|
||||
authorities.add(user.getAuthorities().get(1).getAuthority());
|
||||
|
||||
assertTrue(authorities.contains("ROLE_FROM_ENTRY"));
|
||||
assertTrue(authorities.contains("ROLE_FROM_POPULATOR"));
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
}
|
||||
|
||||
@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());
|
||||
assertEquals("ROLE_FROM_ENTRY", user.getAuthorities().get(0).getAuthority());
|
||||
}
|
||||
|
||||
//~ Inner Classes ==================================================================================================
|
||||
|
||||
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();
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
throw new BadCredentialsException("Authentication failed.");
|
||||
}
|
||||
}
|
||||
|
||||
class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator {
|
||||
String username;
|
||||
|
||||
public List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userCtx, String username) {
|
||||
this.username = username;
|
||||
return AuthorityUtils.createAuthorityList("ROLE_FROM_POPULATOR");
|
||||
}
|
||||
|
||||
String getRequestedUsername() {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap.authenticator;
|
||||
|
||||
import org.springframework.security.Authentication;
|
||||
import org.springframework.security.BadCredentialsException;
|
||||
import org.springframework.security.SpringSecurityMessageSource;
|
||||
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link BindAuthenticator}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class BindAuthenticatorTests extends AbstractLdapIntegrationTests {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private BindAuthenticator authenticator;
|
||||
private Authentication bob;
|
||||
// private Authentication ben;
|
||||
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
public void onSetUp() {
|
||||
authenticator = new BindAuthenticator(getContextSource());
|
||||
authenticator.setMessageSource(new SpringSecurityMessageSource());
|
||||
bob = new UsernamePasswordAuthenticationToken("bob", "bobspassword");
|
||||
// ben = new UsernamePasswordAuthenticationToken("ben", "benspassword");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationWithCorrectPasswordSucceeds() {
|
||||
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"});
|
||||
|
||||
DirContextOperations user = authenticator.authenticate(bob);
|
||||
assertEquals("bob", user.getStringAttribute("uid"));
|
||||
}
|
||||
|
||||
@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) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationWithUserSearch() throws Exception {
|
||||
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=bob,ou=people"));
|
||||
|
||||
authenticator.setUserSearch(new MockUserSearch(ctx));
|
||||
authenticator.afterPropertiesSet();
|
||||
authenticator.authenticate(bob);
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap.authenticator;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.providers.encoding.LdapShaPasswordEncoder;
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link LdapShaPasswordEncoder}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapShaPasswordEncoderTests {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
LdapShaPasswordEncoder sha;
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
sha = new LdapShaPasswordEncoder();
|
||||
}
|
||||
|
||||
@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(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 -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
|
||||
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}"));
|
||||
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap.authenticator;
|
||||
|
||||
import org.springframework.security.ldap.LdapUserSearch;
|
||||
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class MockUserSearch implements LdapUserSearch {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
DirContextOperations user;
|
||||
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
public MockUserSearch() {
|
||||
}
|
||||
|
||||
public MockUserSearch(DirContextOperations user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
public DirContextOperations searchForUser(String username) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap.authenticator;
|
||||
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.BasicAttribute;
|
||||
import javax.naming.directory.BasicAttributes;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.SearchControls;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.integration.junit4.JUnit4Mockery;
|
||||
import org.junit.Test;
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class PasswordComparisonAuthenticatorMockTests {
|
||||
Mockery jmock = new JUnit4Mockery();
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
@Test
|
||||
public void ldapCompareOperationIsUsedWhenPasswordIsNotRetrieved() throws Exception {
|
||||
final DirContext dirCtx = jmock.mock(DirContext.class);
|
||||
final BaseLdapPathContextSource source = jmock.mock(BaseLdapPathContextSource.class);
|
||||
final BasicAttributes attrs = new BasicAttributes();
|
||||
attrs.put(new BasicAttribute("uid", "bob"));
|
||||
|
||||
PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(source);
|
||||
|
||||
authenticator.setUserDnPatterns(new String[] {"cn={0},ou=people"});
|
||||
|
||||
// Get the mock to return an empty attribute set
|
||||
jmock.checking(new Expectations() {{
|
||||
allowing(source).getReadOnlyContext(); will(returnValue(dirCtx));
|
||||
oneOf(dirCtx).getAttributes(with(equal("cn=Bob,ou=people")), with(aNull(String[].class))); will(returnValue(attrs));
|
||||
oneOf(dirCtx).getNameInNamespace(); will(returnValue("dc=springframework,dc=org"));
|
||||
}});
|
||||
|
||||
// Setup a single return value (i.e. success)
|
||||
final Attributes searchResults = new BasicAttributes("", null);
|
||||
|
||||
jmock.checking(new Expectations() {{
|
||||
oneOf(dirCtx).search(with(equal("cn=Bob,ou=people")),
|
||||
with(equal("(userPassword={0})")),
|
||||
with(aNonNull(Object[].class)),
|
||||
with(aNonNull(SearchControls.class)));
|
||||
will(returnValue(searchResults.getAll()));
|
||||
atLeast(1).of(dirCtx).close();
|
||||
}});
|
||||
|
||||
authenticator.authenticate(new UsernamePasswordAuthenticationToken("Bob","bobspassword"));
|
||||
|
||||
jmock.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.providers.ldap.authenticator;
|
||||
|
||||
import org.springframework.security.BadCredentialsException;
|
||||
import org.springframework.security.Authentication;
|
||||
|
||||
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
|
||||
|
||||
import org.springframework.security.providers.encoding.LdapShaPasswordEncoder;
|
||||
import org.springframework.security.providers.encoding.PlaintextPasswordEncoder;
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
|
||||
import org.springframework.security.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link PasswordComparisonAuthenticator}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class PasswordComparisonAuthenticatorTests extends AbstractLdapIntegrationTests {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
private PasswordComparisonAuthenticator authenticator;
|
||||
private Authentication bob;
|
||||
private Authentication ben;
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
public void onSetUp() throws Exception {
|
||||
super.onSetUp();
|
||||
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 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) {}
|
||||
}
|
||||
|
||||
@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 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());
|
||||
}
|
||||
|
||||
@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(expected = IllegalArgumentException.class)
|
||||
public void testPasswordEncoderCantBeNull() {
|
||||
authenticator.setPasswordEncoder(null);
|
||||
}
|
||||
|
||||
@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 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");
|
||||
|
||||
authenticator.setUserSearch(new MockUserSearch(ctx));
|
||||
authenticator.authenticate(new UsernamePasswordAuthenticationToken("shouldntbeused", "bobspassword"));
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class InetOrgPersonTests extends TestCase {
|
||||
|
||||
public void testUsernameIsMappedFromContextUidIfNotSet() {
|
||||
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
|
||||
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
|
||||
|
||||
assertEquals("ghengis", p.getUsername());
|
||||
}
|
||||
|
||||
public void testUsernameIsDifferentFromContextUidIfSet() {
|
||||
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
|
||||
essence.setUsername("joe");
|
||||
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
|
||||
|
||||
assertEquals("joe", p.getUsername());
|
||||
assertEquals("ghengis", p.getUid());
|
||||
}
|
||||
|
||||
public void testAttributesMapCorrectlyFromContext() {
|
||||
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
|
||||
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
|
||||
|
||||
assertEquals("HORS1", p.getCarLicense());
|
||||
assertEquals("ghengis@mongolia", p.getMail());
|
||||
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());
|
||||
}
|
||||
|
||||
public void testPasswordIsSetFromContextUserPassword() {
|
||||
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
|
||||
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
|
||||
|
||||
assertEquals("pillage", p.getPassword());
|
||||
}
|
||||
|
||||
public void testMappingBackToContextMatchesOriginalData() {
|
||||
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);
|
||||
}
|
||||
|
||||
public void testCopyMatchesOriginalData() {
|
||||
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);
|
||||
}
|
||||
|
||||
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("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;
|
||||
}
|
||||
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.security.BadCredentialsException;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.context.SecurityContextHolder;
|
||||
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
|
||||
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.util.AuthorityUtils;
|
||||
|
||||
/**
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapUserDetailsManagerTests extends AbstractLdapIntegrationTests {
|
||||
private static final List<GrantedAuthority> TEST_AUTHORITIES = AuthorityUtils.createAuthorityList("ROLE_CLOWNS","ROLE_ACROBATS");
|
||||
private LdapUserDetailsManager mgr;
|
||||
private SpringSecurityLdapTemplate template;
|
||||
|
||||
public void onSetUp() throws Exception {
|
||||
super.onSetUp();
|
||||
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("ou", "testgroups");
|
||||
template.bind("ou=testgroups", ctx, null);
|
||||
|
||||
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("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());
|
||||
}
|
||||
|
||||
@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");
|
||||
// }
|
||||
|
||||
template.unbind("ou=test people",true);
|
||||
template.unbind("ou=testgroups",true);
|
||||
|
||||
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());
|
||||
|
||||
assertEquals(1, bob.getAuthorities().size());
|
||||
}
|
||||
|
||||
@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 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");
|
||||
|
||||
p.setAuthorities(TEST_AUTHORITIES);
|
||||
|
||||
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);
|
||||
|
||||
mgr.createUser(p.createUserDetails());
|
||||
mgr.setUserDetailsMapper(new InetOrgPersonContextMapper());
|
||||
|
||||
InetOrgPerson don = (InetOrgPerson) mgr.loadUserByUsername("don");
|
||||
|
||||
assertEquals(2, don.getAuthorities().size());
|
||||
|
||||
mgr.deleteUser("don");
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken("johnyossarian", "yossarianspassword", TEST_AUTHORITIES));
|
||||
|
||||
mgr.changePassword("yossarianspassword", "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);
|
||||
|
||||
mgr.createUser(p.createUserDetails());
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken("johnyossarian", "yossarianspassword", TEST_AUTHORITIES));
|
||||
|
||||
mgr.changePassword("wrongpassword", "yossariansnewpassword");
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import javax.naming.directory.BasicAttribute;
|
||||
import javax.naming.directory.BasicAttributes;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.security.util.AuthorityUtils;
|
||||
|
||||
/**
|
||||
* Tests {@link LdapUserDetailsMapper}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapUserDetailsMapperTests extends TestCase {
|
||||
|
||||
public void testMultipleRoleAttributeValuesAreMappedToAuthorities() throws Exception {
|
||||
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
|
||||
mapper.setConvertToUpperCase(false);
|
||||
mapper.setRolePrefix("");
|
||||
|
||||
mapper.setRoleAttributes(new String[] {"userRole"});
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter();
|
||||
|
||||
ctx.setAttributeValues("userRole", new String[] {"X", "Y", "Z"});
|
||||
ctx.setAttributeValue("uid", "ani");
|
||||
|
||||
LdapUserDetailsImpl user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx, "ani", AuthorityUtils.NO_AUTHORITIES);
|
||||
|
||||
assertEquals(3, user.getAuthorities().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* SEC-303. Non-retrieved role attribute causes NullPointerException
|
||||
*/
|
||||
public void testNonRetrievedRoleAttributeIsIgnored() throws Exception {
|
||||
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
|
||||
|
||||
mapper.setRoleAttributes(new String[] {"userRole", "nonRetrievedAttribute"});
|
||||
|
||||
BasicAttributes attrs = new BasicAttributes();
|
||||
attrs.put(new BasicAttribute("userRole", "x"));
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName("cn=someName"));
|
||||
ctx.setAttributeValue("uid", "ani");
|
||||
|
||||
LdapUserDetailsImpl user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx, "ani", AuthorityUtils.NO_AUTHORITIES);
|
||||
|
||||
assertEquals(1, user.getAuthorities().size());
|
||||
assertEquals("ROLE_X", user.getAuthorities().get(0).getAuthority());
|
||||
}
|
||||
|
||||
public void testPasswordAttributeIsMappedCorrectly() throws Exception {
|
||||
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
|
||||
|
||||
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");
|
||||
|
||||
LdapUserDetails user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx, "ani", AuthorityUtils.NO_AUTHORITIES);
|
||||
|
||||
assertEquals("mypassword", user.getPassword());
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package org.springframework.security.userdetails.ldap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.security.GrantedAuthority;
|
||||
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
|
||||
import org.springframework.security.providers.ldap.authenticator.MockUserSearch;
|
||||
import org.springframework.security.userdetails.UserDetails;
|
||||
import org.springframework.security.util.AuthorityUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link LdapUserDetailsService}
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class LdapUserDetailsServiceTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullSearchObject() {
|
||||
new LdapUserDetailsService(null, new MockAuthoritiesPopulator());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullAuthoritiesPopulator() {
|
||||
new LdapUserDetailsService(new MockUserSearch(), null);
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
UserDetails user = service.loadUserByUsername("doesntmatterwegetjoeanyway");
|
||||
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(user.getAuthorities());
|
||||
assertEquals(1, authorities.size());
|
||||
assertTrue(authorities.contains("ROLE_FROM_POPULATOR"));
|
||||
}
|
||||
|
||||
class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator {
|
||||
public List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userCtx, String username) {
|
||||
return AuthorityUtils.createAuthorityList("ROLE_FROM_POPULATOR");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# Logging
|
||||
#
|
||||
# $Id: log4j.properties 3455 2008-12-11 17:00:13Z ltaylor $
|
||||
|
||||
log4j.rootLogger=INFO, stdout
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%p %c{1} - %m%n
|
||||
|
||||
log4j.logger.org.springframework.security=DEBUG
|
||||
log4j.logger.org.springframework.ldap=DEBUG
|
||||
|
||||
log4j.logger.org.apache.directory=ERROR
|
||||
@@ -0,0 +1,82 @@
|
||||
dn: ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: groups
|
||||
|
||||
dn: ou=subgroups,ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: subgroups
|
||||
|
||||
dn: ou=people,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: people
|
||||
|
||||
dn: ou=otherpeople,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: otherpeople
|
||||
|
||||
dn: uid=ben,ou=people,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
cn: Ben Alex
|
||||
sn: Alex
|
||||
uid: ben
|
||||
userPassword: {SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=
|
||||
|
||||
dn: uid=bob,ou=people,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
cn: Bob Hamilton
|
||||
sn: Hamilton
|
||||
uid: bob
|
||||
userPassword: bobspassword
|
||||
|
||||
dn: uid=joe,ou=otherpeople,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
cn: Joe Smeth
|
||||
sn: Smeth
|
||||
uid: joe
|
||||
userPassword: joespassword
|
||||
|
||||
dn: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
cn: Mouse, Jerry
|
||||
sn: Mouse
|
||||
uid: jerry
|
||||
userPassword: jerryspassword
|
||||
|
||||
dn: cn=developers,ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: groupOfNames
|
||||
cn: developers
|
||||
ou: developer
|
||||
member: uid=ben,ou=people,dc=springframework,dc=org
|
||||
member: uid=bob,ou=people,dc=springframework,dc=org
|
||||
|
||||
dn: cn=managers,ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: groupOfNames
|
||||
cn: managers
|
||||
ou: manager
|
||||
member: uid=ben,ou=people,dc=springframework,dc=org
|
||||
member: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
|
||||
|
||||
dn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: groupOfNames
|
||||
cn: submanagers
|
||||
ou: submanager
|
||||
member: uid=ben,ou=people,dc=springframework,dc=org
|
||||
@@ -0,0 +1,16 @@
|
||||
dn: ou=gorillas,dc=monkeymachine,dc=co,dc=uk
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: gorillas
|
||||
|
||||
dn: uid=pg,ou=gorillas,dc=monkeymachine,dc=co,dc=uk
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
cn: Pierre
|
||||
sn: Gorille
|
||||
uid: pg
|
||||
userPassword: password
|
||||
|
||||
|
||||
Reference in New Issue
Block a user