Initial LDAP provider checkin.
This commit is contained in:
parent
6e3a192a32
commit
ce3d6f2129
|
@ -0,0 +1,249 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.net.URI;
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.CommunicationException;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
|
||||||
|
import org.springframework.dao.DataAccessResourceFailureException;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.acegisecurity.BadCredentialsException;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates the information for connecting to an LDAP server and provides an
|
||||||
|
* access point for obtaining <tt>DirContext</tt> references.
|
||||||
|
* <p>
|
||||||
|
* The directory location is configured using by setting the <tt>url</tt> property.
|
||||||
|
* This should be in the form <tt>ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org</tt>.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* To obtain an initial context, th client calls the <tt>newInitialDirContext</tt>
|
||||||
|
* method. There are two signatures - one with no arguments and one which allows
|
||||||
|
* binding with a specific username and password.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* The no-args version will bind anonymously or if a manager login has been configured
|
||||||
|
* using the properties <tt>managerDn</tt> and <tt>managerPassword</tt> it will bind as
|
||||||
|
* that user.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Connection pooling is enabled for anonymous or manager connections, but not when binding
|
||||||
|
* as a specific user.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/connect/pool.html">The Java tutorial's guide to
|
||||||
|
* Connection Pooling</a>
|
||||||
|
*
|
||||||
|
* @author Robert Sanders
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class DefaultInitialDirContextFactory implements InitialDirContextFactory,
|
||||||
|
InitializingBean {
|
||||||
|
|
||||||
|
//~ Static fields/initializers =============================================
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(DefaultInitialDirContextFactory.class);
|
||||||
|
|
||||||
|
private static final String CONNECTION_POOL_KEY = "com.sun.jndi.ldap.connect.pool";
|
||||||
|
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The LDAP url of the server (and root context) to connect to.
|
||||||
|
* TODO: Allow a backup URL for a replication server.
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root DN. This is worked out from the url.
|
||||||
|
* It is used by client classes when forming a full DN for
|
||||||
|
* bind authentication (for example).
|
||||||
|
*/
|
||||||
|
private String rootDn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If your LDAP server does not allow anonymous searches then
|
||||||
|
* you will need to provide a "manager" user's DN to log in with.
|
||||||
|
*/
|
||||||
|
private String managerDn = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The manager user's password.
|
||||||
|
*/
|
||||||
|
private String managerPassword = null;
|
||||||
|
|
||||||
|
/** Type of authentication within LDAP; default is simple. */
|
||||||
|
private String authenticationType = "simple";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The INITIAL_CONTEXT_FACTORY used to create the JNDI Factory.
|
||||||
|
* Default is "com.sun.jndi.ldap.LdapCtxFactory"; you <b>should not</b>
|
||||||
|
* need to set this unless you have unusual needs.
|
||||||
|
*/
|
||||||
|
private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
|
||||||
|
|
||||||
|
/** Allows extra environment variables to be added at config time. */
|
||||||
|
private Map extraEnvVars = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the LDAP Connection pool; if true, then the
|
||||||
|
* LDAP environment property "com.sun.jndi.ldap.connect.pool" is added
|
||||||
|
* to any other JNDI properties.
|
||||||
|
*/
|
||||||
|
private boolean useConnectionPool = true;
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects anonymously unless a manager user has been specified, in which case
|
||||||
|
* it will bind as the manager.
|
||||||
|
*
|
||||||
|
* @return the resulting
|
||||||
|
*/
|
||||||
|
public DirContext newInitialDirContext() {
|
||||||
|
|
||||||
|
if (managerDn != null) {
|
||||||
|
return newInitialDirContext(managerDn, managerPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
return connect(getEnvironment());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirContext newInitialDirContext(String username, String password) {
|
||||||
|
Hashtable env = getEnvironment();
|
||||||
|
|
||||||
|
// Don't pool connections for individual users
|
||||||
|
if(!username.equals(managerDn)) {
|
||||||
|
env.remove(CONNECTION_POOL_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
env.put(Context.SECURITY_PRINCIPAL, username);
|
||||||
|
env.put(Context.SECURITY_CREDENTIALS, password);
|
||||||
|
|
||||||
|
return connect(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The Hashtable describing the base DirContext that will be created, minus the username/password if any.
|
||||||
|
*/
|
||||||
|
protected Hashtable getEnvironment() {
|
||||||
|
Hashtable env = new Hashtable();
|
||||||
|
|
||||||
|
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
|
||||||
|
env.put(Context.PROVIDER_URL, url);
|
||||||
|
env.put(Context.SECURITY_AUTHENTICATION, authenticationType);
|
||||||
|
|
||||||
|
if (useConnectionPool) {
|
||||||
|
env.put(CONNECTION_POOL_KEY, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((extraEnvVars != null) && (extraEnvVars.size() > 0)) {
|
||||||
|
env.putAll(extraEnvVars);
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InitialDirContext connect(Hashtable env) {
|
||||||
|
|
||||||
|
// Prints the password, so don't use except for debugging.
|
||||||
|
// logger.debug("Creating initial context with env " + env);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new InitialDirContext(env);
|
||||||
|
|
||||||
|
} catch(CommunicationException ce) {
|
||||||
|
throw new DataAccessResourceFailureException("Unable to connect to LDAP Server.", ce);
|
||||||
|
} catch(javax.naming.AuthenticationException ae) {
|
||||||
|
throw new BadCredentialsException("Authentication to LDAP server failed.", ae);
|
||||||
|
} catch (NamingException nx) {
|
||||||
|
throw new LdapDataAccessException("Failed to obtain InitialDirContext", nx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
Assert.hasLength(url, "An LDAP connection URL must be supplied.");
|
||||||
|
|
||||||
|
URI uri = new URI(url);
|
||||||
|
|
||||||
|
rootDn = uri.getPath();
|
||||||
|
|
||||||
|
if(rootDn.startsWith("/")) { // I think this is always true.
|
||||||
|
rootDn = rootDn.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.isTrue(uri.getScheme().equals("ldap"), "Ldap URL must start with 'ldap://'");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root DN of the configured provider URL. For example,
|
||||||
|
* if the URL is <tt>ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org</tt>
|
||||||
|
* the value will be <tt>dc=acegisecurity,dc=org</tt>.
|
||||||
|
*
|
||||||
|
* @return the root DN calculated from the path of the LDAP url.
|
||||||
|
*/
|
||||||
|
public String getRootDn() {
|
||||||
|
return rootDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationType(String authenticationType) {
|
||||||
|
Assert.hasLength(authenticationType);
|
||||||
|
this.authenticationType = authenticationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitialContextFactory(String initialContextFactory) {
|
||||||
|
Assert.hasLength(initialContextFactory);
|
||||||
|
this.initialContextFactory = initialContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param managerDn The name of the "manager" user for default authentication.
|
||||||
|
*/
|
||||||
|
public void setManagerDn(String managerDn) {
|
||||||
|
this.managerDn = managerDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param managerPassword The "manager" user's password.
|
||||||
|
*/
|
||||||
|
public void setManagerPassword(String managerPassword) {
|
||||||
|
this.managerPassword = managerPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param extraEnvVars extra environment variables to be added at config time.
|
||||||
|
*/
|
||||||
|
public void setExtraEnvVars(Map extraEnvVars) {
|
||||||
|
this.extraEnvVars = extraEnvVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access point for obtaining LDAP contexts.
|
||||||
|
*
|
||||||
|
* @see DefaultInitialDirContextFactory
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public interface InitialDirContextFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an initial context without specific user information.
|
||||||
|
*/
|
||||||
|
DirContext newInitialDirContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an initial context by binding as a specific user.
|
||||||
|
*/
|
||||||
|
DirContext newInitialDirContext(String userDn, String password);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The DN of the contexts returned by this factory.
|
||||||
|
*/
|
||||||
|
String getRootDn();
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;
|
||||||
|
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.acegisecurity.*;
|
||||||
|
import org.acegisecurity.userdetails.UserDetails;
|
||||||
|
import org.acegisecurity.userdetails.User;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class responsible for LDAP authentication.
|
||||||
|
*
|
||||||
|
* <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}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <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.acegisecurity.providers.ldap.authenticator.BindAuthenticator BindAuthenticator}
|
||||||
|
* which authenticates the user by "binding" as that user, and
|
||||||
|
* {@link org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator PasswordComparisonAuthenticator}
|
||||||
|
* which performs a comparison of the supplied password with the value stored in the directory,
|
||||||
|
* either by retrieving the password or performing 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).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <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 org.acegisecurity.providers.ldap.populator.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.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
|
||||||
|
//~ Static fields/initializers =============================================
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(LdapAuthenticationProvider.class);
|
||||||
|
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private LdapAuthenticator authenticator;
|
||||||
|
|
||||||
|
private LdapAuthoritiesPopulator ldapAuthoritiesPopulator;
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
|
||||||
|
logger.debug("Retrieving user " + username);
|
||||||
|
|
||||||
|
String password = (String)authentication.getCredentials();
|
||||||
|
Assert.notNull(password, "Null password was supplied in authentication token");
|
||||||
|
|
||||||
|
LdapUserDetails ldapUser = authenticator.authenticate(username, password);
|
||||||
|
|
||||||
|
return createUserDetails(username, password, ldapUser.getDn(), ldapUser.getAttributes());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doAfterPropertiesSet() throws Exception {
|
||||||
|
super.doAfterPropertiesSet();
|
||||||
|
Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
|
||||||
|
Assert.notNull(ldapAuthoritiesPopulator, "An LdapAuthoritiesPopulator must be supplied");
|
||||||
|
|
||||||
|
// TODO: Check that the role attributes specified for the populator will be retrieved
|
||||||
|
// by the authenticator. If not, add them to the authenticator's list and log a
|
||||||
|
// warning.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the user final <tt>UserDetails</tt> object that will be returned by the provider
|
||||||
|
* once the user has been authenticated.
|
||||||
|
* <p>
|
||||||
|
* The <tt>LdapAuthoritiesPopulator</tt> will be used to create the granted authorites for the
|
||||||
|
* user.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Can be overridden to customize the mapping of user attributes to additional user information.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param username The user login, as passed to the provider
|
||||||
|
* @param password The submitted password
|
||||||
|
* @param userDn The DN of the user in the Ldap system.
|
||||||
|
* @param attributes The user attributes retrieved from the Ldap system.
|
||||||
|
* @return The UserDetails for the successfully authenticated user.
|
||||||
|
*/
|
||||||
|
protected UserDetails createUserDetails(String username, String password, String userDn, Attributes attributes) {
|
||||||
|
|
||||||
|
return new User(username, password, true, true, true, true,
|
||||||
|
ldapAuthoritiesPopulator.getGrantedAuthorities(username, userDn, attributes));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticator(LdapAuthenticator authenticator) {
|
||||||
|
this.authenticator = authenticator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLdapAuthoritiesPopulator(LdapAuthoritiesPopulator ldapAuthoritiesPopulator) {
|
||||||
|
this.ldapAuthoritiesPopulator = ldapAuthoritiesPopulator;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public interface LdapAuthenticator {
|
||||||
|
/**
|
||||||
|
* Authenticates as a user and obtains additional user information
|
||||||
|
* from the directory.
|
||||||
|
*
|
||||||
|
* @param username the user's login name (<em>not</em> their DN).
|
||||||
|
* @param password the user's password supplied at login.
|
||||||
|
* @return the details of the successfully authenticated user.
|
||||||
|
*/
|
||||||
|
LdapUserDetails authenticate(String username, String password);
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import org.acegisecurity.GrantedAuthority;
|
||||||
|
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param username the login name which was passed to the LDAP provider.
|
||||||
|
* @param userDn the full DN of the user
|
||||||
|
* @param userAttributes the user's LDAP attributes that were retrieved from the directory.
|
||||||
|
* @return the granted authorities for the given user.
|
||||||
|
*/
|
||||||
|
GrantedAuthority[] getGrantedAuthorities(String username, String userDn, Attributes userAttributes);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import org.springframework.dao.UncategorizedDataAccessException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to wrap unexpected NamingExceptions while accessing the LDAP server.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class LdapDataAccessException extends UncategorizedDataAccessException {
|
||||||
|
|
||||||
|
public LdapDataAccessException(String msg, Throwable ex) {
|
||||||
|
super(msg, ex);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import org.acegisecurity.userdetails.User;
|
||||||
|
import org.acegisecurity.GrantedAuthority;
|
||||||
|
import org.acegisecurity.GrantedAuthorityImpl;
|
||||||
|
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user representation which is used internally by the Ldap provider.
|
||||||
|
*
|
||||||
|
* It 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 LdapUserDetails {
|
||||||
|
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private String dn;
|
||||||
|
private Attributes attributes;
|
||||||
|
|
||||||
|
//~ Constructors ===========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param dn the full DN of the user
|
||||||
|
* @param attributes any attributes loaded from the user's directory entry.
|
||||||
|
*/
|
||||||
|
public LdapUserDetails(String dn, Attributes attributes) {
|
||||||
|
this.dn = dn;
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public String getDn() {
|
||||||
|
return dn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRelativeName(DirContext ctx) throws NamingException {
|
||||||
|
return LdapUtils.getRelativeName(dn, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attributes getAttributes() {
|
||||||
|
return (Attributes)attributes.clone();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LDAP Utility methods.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class LdapUtils {
|
||||||
|
//~ Static fields/initializers =============================================
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(LdapUtils.class);
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public static void closeContext(Context ctx) {
|
||||||
|
try {
|
||||||
|
if (ctx != null) {
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
} catch (NamingException e) {
|
||||||
|
logger.error("Failed to close context.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 escapeNameForFilter(String name) {
|
||||||
|
// TODO: Implement escaping as defined in RFC 2254
|
||||||
|
// Think this is probably not needed as filter args should be escaped automatically
|
||||||
|
// by the search methods.
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the part of a DN relative to a supplied base context.
|
||||||
|
* <p>
|
||||||
|
* If the DN is "cn=bob,ou=people,dc=acegisecurity,dc=org" and the base context
|
||||||
|
* name is "ou=people,dc=acegisecurity,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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(baseDn.equals(fullDn)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = fullDn.lastIndexOf(baseDn);
|
||||||
|
|
||||||
|
Assert.isTrue(index > 0, "Context base DN is not contained in the full DN");
|
||||||
|
|
||||||
|
// remove the base name and preceding comma.
|
||||||
|
return fullDn.substring(0, index - 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.LdapAuthenticator;
|
||||||
|
import org.acegisecurity.providers.ldap.InitialDirContextFactory;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public abstract class AbstractLdapAuthenticator implements LdapAuthenticator,
|
||||||
|
InitializingBean {
|
||||||
|
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private String userDnPattern = null;
|
||||||
|
private MessageFormat userDnFormat = null;
|
||||||
|
private InitialDirContextFactory initialDirContextFactory;
|
||||||
|
private LdapUserSearch userSearch;
|
||||||
|
private String[] userAttributes = null;
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the DN of the user, worked out from the userDNPattern property.
|
||||||
|
* The returned value includes the root DN of the provider
|
||||||
|
* URL used to configure the <tt>InitialDirContextfactory</tt>.
|
||||||
|
*/
|
||||||
|
protected String getUserDn(String username) {
|
||||||
|
if(userDnFormat == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String rootDn = initialDirContextFactory.getRootDn();
|
||||||
|
String userDn;
|
||||||
|
|
||||||
|
synchronized( userDnFormat ) {
|
||||||
|
userDn = userDnFormat.format(new String[] {username});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rootDn.length() > 0) {
|
||||||
|
userDn = userDn + "," + rootDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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".
|
||||||
|
*/
|
||||||
|
public void setUserDnPattern(String dnPattern) {
|
||||||
|
this.userDnPattern = dnPattern;
|
||||||
|
userDnFormat = null;
|
||||||
|
|
||||||
|
if(dnPattern != null) {
|
||||||
|
userDnFormat = new MessageFormat(dnPattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getUserAttributes() {
|
||||||
|
return userAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserDnPattern() {
|
||||||
|
return userDnPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserSearch(LdapUserSearch userSearch) {
|
||||||
|
this.userSearch = userSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LdapUserSearch getUserSearch() {
|
||||||
|
return userSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) {
|
||||||
|
this.initialDirContextFactory = initialDirContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the user attributes which will be retrieved from the directory.
|
||||||
|
*
|
||||||
|
* @param userAttributes
|
||||||
|
*/
|
||||||
|
public void setUserAttributes(String[] userAttributes) {
|
||||||
|
this.userAttributes = userAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InitialDirContextFactory getInitialDirContextFactory() {
|
||||||
|
return initialDirContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
Assert.notNull(initialDirContextFactory, "initialDirContextFactory must be supplied.");
|
||||||
|
Assert.isTrue(userDnPattern != null || userSearch != null, "Either an LdapUserSearch or DN pattern (or both) must be supplied.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.*;
|
||||||
|
import org.acegisecurity.BadCredentialsException;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An authenticator which binds as a user.
|
||||||
|
*
|
||||||
|
* @see AbstractLdapAuthenticator
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class BindAuthenticator extends AbstractLdapAuthenticator {
|
||||||
|
|
||||||
|
//~ Static fields/initializers =============================================
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(BindAuthenticator.class);
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public LdapUserDetails authenticate(String username, String password) {
|
||||||
|
|
||||||
|
String dn = getUserDn(username);
|
||||||
|
LdapUserDetails user = null;
|
||||||
|
|
||||||
|
// If DN is pattern is configured, try authenticating with that directly
|
||||||
|
if(dn != null) {
|
||||||
|
user = authenticateWithDn(dn, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise use the configured locator to find the user
|
||||||
|
// and authenticate with the returned DN.
|
||||||
|
if(user == null && getUserSearch() != null) {
|
||||||
|
LdapUserDetails userFromSearch = getUserSearch().searchForUser(username);
|
||||||
|
user = authenticateWithDn(userFromSearch.getDn(), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(user == null) {
|
||||||
|
throw new BadCredentialsException("Failed to authenticate as " + username);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private LdapUserDetails authenticateWithDn(String userDn, String password) {
|
||||||
|
DirContext ctx = null;
|
||||||
|
LdapUserDetails user = null;
|
||||||
|
Attributes attributes = null;
|
||||||
|
|
||||||
|
if(logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Binding with DN = " + userDn);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx = getInitialDirContextFactory().newInitialDirContext(userDn, password);
|
||||||
|
attributes = ctx.getAttributes(
|
||||||
|
LdapUtils.getRelativeName(userDn, ctx),
|
||||||
|
getUserAttributes());
|
||||||
|
user = new LdapUserDetails(userDn, attributes);
|
||||||
|
|
||||||
|
} catch(NamingException ne) {
|
||||||
|
throw new LdapDataAccessException("Failed to load attributes for user " + userDn, ne);
|
||||||
|
} catch(BadCredentialsException 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.
|
||||||
|
logger.debug("Failed to bind as " + userDn, e);
|
||||||
|
} finally {
|
||||||
|
LdapUtils.closeContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.*;
|
||||||
|
import org.acegisecurity.userdetails.UsernameNotFoundException;
|
||||||
|
import org.acegisecurity.BadCredentialsException;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import javax.naming.directory.SearchControls;
|
||||||
|
import javax.naming.directory.SearchResult;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LdapUserSearch implementation which uses an Ldap filter to locate the user.
|
||||||
|
*
|
||||||
|
* @author Robert Sanders
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class FilterBasedLdapUserSearch implements LdapUserSearch {
|
||||||
|
//~ Static fields/initializers =============================================
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(FilterBasedLdapUserSearch.class);
|
||||||
|
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context name to search in, relative to the root DN of the configured
|
||||||
|
* InitialDirContextFactory.
|
||||||
|
*/
|
||||||
|
private String searchBase = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true then searches the entire subtree as identified by context,
|
||||||
|
* if false (the default) then only searches the level identified by the context.
|
||||||
|
*/
|
||||||
|
// private boolean searchSubtree = false;
|
||||||
|
|
||||||
|
private int searchScope = SearchControls.ONELEVEL_SCOPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* TODO: more examples.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String searchFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time (in milliseconds) which to wait before the search fails;
|
||||||
|
* the default is zero, meaning forever.
|
||||||
|
*/
|
||||||
|
private int searchTimeLimit = 0;
|
||||||
|
|
||||||
|
private InitialDirContextFactory initialDirContextFactory;
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the LdapUserDetails containing the user's information, or null if
|
||||||
|
* no SearchResult is found.
|
||||||
|
*
|
||||||
|
* @param username the username to search for.
|
||||||
|
*/
|
||||||
|
public LdapUserDetails searchForUser(String username) {
|
||||||
|
DirContext ctx = initialDirContextFactory.newInitialDirContext();
|
||||||
|
SearchControls ctls = new SearchControls();
|
||||||
|
ctls.setTimeLimit( searchTimeLimit );
|
||||||
|
ctls.setSearchScope( searchScope );
|
||||||
|
|
||||||
|
try {
|
||||||
|
String[] args = new String[] { LdapUtils.escapeNameForFilter(username) };
|
||||||
|
|
||||||
|
NamingEnumeration results = ctx.search(searchBase, searchFilter, args, ctls);
|
||||||
|
|
||||||
|
if (!results.hasMore()) {
|
||||||
|
throw new UsernameNotFoundException("User " + username + " not found in directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchResult searchResult = (SearchResult)results.next();
|
||||||
|
|
||||||
|
if(results.hasMore()) {
|
||||||
|
throw new BadCredentialsException("Expected a single user but search returned multiple results");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuffer userDn = new StringBuffer(searchResult.getName());
|
||||||
|
|
||||||
|
if(searchBase.length() > 0) {
|
||||||
|
userDn.append(",");
|
||||||
|
userDn.append(searchBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
userDn.append(",");
|
||||||
|
userDn.append(ctx.getNameInNamespace());
|
||||||
|
|
||||||
|
return new LdapUserDetails(userDn.toString(), searchResult.getAttributes());
|
||||||
|
|
||||||
|
} catch(NamingException ne) {
|
||||||
|
throw new LdapDataAccessException("User Couldn't be found due to exception", ne);
|
||||||
|
} finally {
|
||||||
|
LdapUtils.closeContext(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
Assert.notNull(initialDirContextFactory, "initialDirContextFactory must be set");
|
||||||
|
Assert.notNull(searchFilter, "searchFilter must be set.");
|
||||||
|
|
||||||
|
if(searchBase.equals("")) {
|
||||||
|
logger.info("No search base DN supplied. Search will be performed from the root: " +
|
||||||
|
initialDirContextFactory.getRootDn());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) {
|
||||||
|
this.initialDirContextFactory = initialDirContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchFilter(String searchFilter) {
|
||||||
|
this.searchFilter = searchFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchSubtree(boolean searchSubtree) {
|
||||||
|
// this.searchSubtree = searchSubtree;
|
||||||
|
this.searchScope = searchSubtree ?
|
||||||
|
SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchTimeLimit(int searchTimeLimit) {
|
||||||
|
this.searchTimeLimit = searchTimeLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchBase(String searchBase) {
|
||||||
|
this.searchBase = searchBase;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.encoding.ShaPasswordEncoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of {@link ShaPasswordEncoder} which always uses
|
||||||
|
* Base-64 encoding and prepends the string
|
||||||
|
* "{SHA}" to the encoded hash.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class LdapShaPasswordEncoder extends ShaPasswordEncoder {
|
||||||
|
public LdapShaPasswordEncoder() {
|
||||||
|
super.setEncodeHashAsBase64(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encodePassword(String rawPass, Object salt) {
|
||||||
|
return "{SHA}" + super.encodePassword(rawPass, salt);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.LdapUserDetails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 an LdapUserDetails object containing the user's full DN and requested attributes.
|
||||||
|
* TODO: Need to optionally supply required attributes here for the search.
|
||||||
|
*/
|
||||||
|
LdapUserDetails searchForUser(String username);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.LdapUserDetails;
|
||||||
|
import org.acegisecurity.providers.ldap.LdapUtils;
|
||||||
|
import org.acegisecurity.providers.encoding.PasswordEncoder;
|
||||||
|
import org.acegisecurity.BadCredentialsException;
|
||||||
|
import org.acegisecurity.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.directory.SearchControls;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.Attribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link org.acegisecurity.providers.ldap.LdapAuthenticator LdapAuthenticator}
|
||||||
|
* which compares the login password with the value stored in the directory.
|
||||||
|
* <p>
|
||||||
|
* This can be achieved either by retrieving the password attribute for the user
|
||||||
|
* and comparing it locally, or by peforming an LDAP "compare" operation.
|
||||||
|
* If the password attribute (default "userPassword") is found in the retrieved
|
||||||
|
* attributes it will be compared locally. If not, the remote comparison will be
|
||||||
|
* attempted.
|
||||||
|
* </p>
|
||||||
|
* <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}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class PasswordComparisonAuthenticator extends AbstractLdapAuthenticator {
|
||||||
|
//~ Static fields/initializers =============================================
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(PasswordComparisonAuthenticator.class);
|
||||||
|
|
||||||
|
private static final String[] NO_ATTRS = new String[0];
|
||||||
|
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private String passwordAttributeName = "userPassword";
|
||||||
|
|
||||||
|
private String passwordCompareFilter = "(userPassword={0})";
|
||||||
|
|
||||||
|
private PasswordEncoder passwordEncoder = new LdapShaPasswordEncoder();
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public LdapUserDetails authenticate(String username, String password) {
|
||||||
|
|
||||||
|
// locate the user and check the password
|
||||||
|
String userDn = getUserDn(username);
|
||||||
|
LdapUserDetails user = null;
|
||||||
|
|
||||||
|
DirContext ctx = getInitialDirContextFactory().newInitialDirContext();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(userDn != null) {
|
||||||
|
String relativeName = LdapUtils.getRelativeName(userDn, ctx);
|
||||||
|
|
||||||
|
user = new LdapUserDetails(userDn,
|
||||||
|
ctx.getAttributes(relativeName, getUserAttributes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(user == null && getUserSearch() != null) {
|
||||||
|
user = getUserSearch().searchForUser(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(user == null) {
|
||||||
|
throw new UsernameNotFoundException(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
Attribute passwordAttribute = user.getAttributes().get(passwordAttributeName);
|
||||||
|
|
||||||
|
if(passwordAttribute != null) {
|
||||||
|
Object retrievedPassword = passwordAttribute.get();
|
||||||
|
|
||||||
|
if(!(retrievedPassword instanceof String)) {
|
||||||
|
// Assume it's binary
|
||||||
|
retrievedPassword = new String((byte[])retrievedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!verifyPassword(password, (String)retrievedPassword)) {
|
||||||
|
throw new BadCredentialsException("Invalid password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
doPasswordCompare(ctx, user.getRelativeName(ctx), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
} catch(NamingException ne) {
|
||||||
|
throw new BadCredentialsException("Authentication failed due to exception ", ne);
|
||||||
|
} finally {
|
||||||
|
LdapUtils.closeContext(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the use of both simple and hashed passwords in the directory.
|
||||||
|
*/
|
||||||
|
private boolean verifyPassword(String password, String ldapPassword) {
|
||||||
|
if(ldapPassword.equals(password)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(passwordEncoder.isPasswordValid(ldapPassword, password, null)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doPasswordCompare(DirContext ctx, String name, String password) throws NamingException {
|
||||||
|
if(logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Performing LDAP compare of password for " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
password = passwordEncoder.encodePassword(password, null);
|
||||||
|
byte[] passwordBytes = LdapUtils.getUtf8Bytes(password);
|
||||||
|
|
||||||
|
SearchControls ctls = new SearchControls();
|
||||||
|
ctls.setReturningAttributes(NO_ATTRS);
|
||||||
|
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
||||||
|
|
||||||
|
NamingEnumeration results = ctx.search(name, passwordCompareFilter,
|
||||||
|
new Object[]{passwordBytes}, ctls);
|
||||||
|
|
||||||
|
if(!results.hasMore()) {
|
||||||
|
throw new BadCredentialsException("Password comparison failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordAttributeName(String passwordAttribute) {
|
||||||
|
Assert.hasLength(passwordAttribute, "passwordAttribute must not be empty or null");
|
||||||
|
this.passwordAttributeName = passwordAttribute;
|
||||||
|
this.passwordCompareFilter = "(" + passwordAttributeName + "={0})";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
|
||||||
|
Assert.notNull(passwordEncoder, "Password Encoder must not be null.");
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,285 @@
|
||||||
|
/* Copyright 2004, 2005 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.acegisecurity.providers.ldap.populator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
|
||||||
|
import org.acegisecurity.providers.ldap.LdapDataAccessException;
|
||||||
|
import org.acegisecurity.providers.ldap.InitialDirContextFactory;
|
||||||
|
import org.acegisecurity.providers.ldap.LdapUtils;
|
||||||
|
import org.acegisecurity.GrantedAuthority;
|
||||||
|
import org.acegisecurity.GrantedAuthorityImpl;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
import javax.naming.directory.Attribute;
|
||||||
|
import javax.naming.directory.SearchControls;
|
||||||
|
import javax.naming.directory.SearchResult;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default strategy for obtaining user role information from the directory.
|
||||||
|
* <p>
|
||||||
|
* It obtains roles by
|
||||||
|
* <ul>
|
||||||
|
* <li>Reading the values of the roles specified by the attribute names in the
|
||||||
|
* <tt>userRoleAttributes</tt> </li>
|
||||||
|
* <li>Performing a search for "groups" the user is a member of and adding
|
||||||
|
* those to the list of roles.</li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* If the <tt>userRolesAttributes</tt> property is set, any matching
|
||||||
|
* attributes amongst those retrieved for the user will have their values added
|
||||||
|
* to the list of roles.
|
||||||
|
* If <tt>userRolesAttributes</tt> is null, no attributes will be mapped to roles.
|
||||||
|
* </p>
|
||||||
|
* <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=acegisecurity,dc=org</tt>
|
||||||
|
* and a group called "developers" with "ben" and "marissa" as members:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* dn: ou=groups,dc=acegisecurity,dc=org
|
||||||
|
* objectClass: top
|
||||||
|
* objectClass: organizationalUnit
|
||||||
|
* ou: groups
|
||||||
|
*
|
||||||
|
* dn: cn=developers,ou=groups,dc=acegisecurity,dc=org
|
||||||
|
* objectClass: groupOfNames
|
||||||
|
* objectClass: top
|
||||||
|
* cn: developers
|
||||||
|
* description: Acegi Security Developers
|
||||||
|
* member: uid=ben,ou=people,dc=acegisecurity,dc=org
|
||||||
|
* member: uid=marissa,ou=people,dc=acegisecurity,dc=org
|
||||||
|
* ou: developer
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
* <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 specify which attribute defines the role name by
|
||||||
|
* setting the <tt>groupRoleAttribute</tt> property (the default is "cn").
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <bean id="ldapAuthoritiesPopulator" class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
|
||||||
|
* TODO
|
||||||
|
* </bean>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator,
|
||||||
|
InitializingBean {
|
||||||
|
//~ Static fields/initializers =============================================
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(DefaultLdapAuthoritiesPopulator.class);
|
||||||
|
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
/** Attributes of the User's LDAP Object that contain role name information. */
|
||||||
|
private String[] userRoleAttributes = null;
|
||||||
|
|
||||||
|
private String rolePrefix = "";
|
||||||
|
|
||||||
|
/** The base DN from which the search for group membership should be performed */
|
||||||
|
private String groupSearchBase = null;
|
||||||
|
|
||||||
|
/** The pattern to be used for the user search. {0} is the user's DN */
|
||||||
|
private String groupSearchFilter = "(member={0})";
|
||||||
|
|
||||||
|
/** The ID of the attribute which contains the role name for a group */
|
||||||
|
private String groupRoleAttribute = "cn";
|
||||||
|
|
||||||
|
/** Whether group searches should be performed over the full sub-tree from the base DN */
|
||||||
|
// private boolean searchSubtree = false;
|
||||||
|
|
||||||
|
/** Internal variable, tied to searchSubTree property */
|
||||||
|
private int searchScope = SearchControls.ONELEVEL_SCOPE;
|
||||||
|
|
||||||
|
private boolean convertToUpperCase = true;
|
||||||
|
|
||||||
|
/** An initial context factory is only required if searching for groups is required. */
|
||||||
|
private InitialDirContextFactory initialDirContextFactory = null;
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param username the login name passed to the authentication provider.
|
||||||
|
* @param userDn the user's DN.
|
||||||
|
* @param userAttributes the attributes retrieved from the user's directory entry.
|
||||||
|
* @return the full set of roles granted to the user.
|
||||||
|
*/
|
||||||
|
public GrantedAuthority[] getGrantedAuthorities(String username, String userDn, Attributes userAttributes) {
|
||||||
|
logger.debug("Getting authorities for user " + userDn);
|
||||||
|
|
||||||
|
Set roles = getRolesFromUserAttributes(userDn, userAttributes);
|
||||||
|
|
||||||
|
Set groupRoles = getGroupMembershipRoles(userDn, userAttributes);
|
||||||
|
|
||||||
|
if(groupRoles != null) {
|
||||||
|
roles.addAll(groupRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (GrantedAuthority[])roles.toArray(new GrantedAuthority[roles.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set getRolesFromUserAttributes(String userDn, Attributes userAttributes) {
|
||||||
|
Set userRoles = new HashSet();
|
||||||
|
|
||||||
|
for(int i=0; userRoleAttributes != null && i < userRoleAttributes.length; i++) {
|
||||||
|
Attribute roleAttribute = userAttributes.get(userRoleAttributes[i]);
|
||||||
|
|
||||||
|
addAttributeValuesToRoleSet(roleAttribute, userRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for groups the user is a member of.
|
||||||
|
*
|
||||||
|
* @param userDn the user's distinguished name.
|
||||||
|
* @param userAttributes
|
||||||
|
* @return the set of roles obtained from a group membership search.
|
||||||
|
*/
|
||||||
|
protected Set getGroupMembershipRoles(String userDn, Attributes userAttributes) {
|
||||||
|
Set userRoles = new HashSet();
|
||||||
|
|
||||||
|
if (groupSearchBase == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirContext ctx = initialDirContextFactory.newInitialDirContext();
|
||||||
|
SearchControls ctls = new SearchControls();
|
||||||
|
|
||||||
|
ctls.setSearchScope(searchScope);
|
||||||
|
ctls.setReturningAttributes(new String[] {groupRoleAttribute});
|
||||||
|
|
||||||
|
try {
|
||||||
|
NamingEnumeration groups =
|
||||||
|
ctx.search(groupSearchBase, groupSearchFilter, new String[]{userDn}, ctls);
|
||||||
|
|
||||||
|
while (groups.hasMore()) {
|
||||||
|
SearchResult result = (SearchResult) groups.next();
|
||||||
|
Attributes attrs = result.getAttributes();
|
||||||
|
|
||||||
|
// There should only be one role attribute.
|
||||||
|
NamingEnumeration groupRoleAttributes = attrs.getAll();
|
||||||
|
|
||||||
|
while(groupRoleAttributes.hasMore()) {
|
||||||
|
Attribute roleAttribute = (Attribute) groupRoleAttributes.next();
|
||||||
|
|
||||||
|
addAttributeValuesToRoleSet(roleAttribute, userRoles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NamingException e) {
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
LdapUtils.closeContext(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAttributeValuesToRoleSet(Attribute roleAttribute, Set roles) {
|
||||||
|
if(roleAttribute == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
NamingEnumeration attributeRoles = roleAttribute.getAll();
|
||||||
|
|
||||||
|
while(attributeRoles.hasMore()) {
|
||||||
|
Object role = attributeRoles.next();
|
||||||
|
|
||||||
|
// We only handle Strings for the time being
|
||||||
|
if(role instanceof String) {
|
||||||
|
if(convertToUpperCase) {
|
||||||
|
role = ((String)role).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
roles.add(new GrantedAuthorityImpl(rolePrefix + role));
|
||||||
|
} else {
|
||||||
|
logger.warn("Non-String value found for role attribute " + roleAttribute.getID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(NamingException ne) {
|
||||||
|
throw new LdapDataAccessException("Error retrieving values for role attribute " +
|
||||||
|
roleAttribute.getID(), ne);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String[] getUserRoleAttributes() {
|
||||||
|
return userRoleAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserRoleAttributes(String[] userRoleAttributes) {
|
||||||
|
this.userRoleAttributes = userRoleAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRolePrefix(String rolePrefix) {
|
||||||
|
Assert.notNull(rolePrefix, "rolePrefix must not be null");
|
||||||
|
this.rolePrefix = rolePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupSearchBase(String groupSearchBase) {
|
||||||
|
this.groupSearchBase = groupSearchBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupSearchFilter(String groupSearchFilter) {
|
||||||
|
Assert.notNull(groupSearchFilter, "groupSearchFilter must not be null");
|
||||||
|
this.groupSearchFilter = groupSearchFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupRoleAttribute(String groupRoleAttribute) {
|
||||||
|
Assert.notNull(groupRoleAttribute, "groupRoleAttribute must not be null");
|
||||||
|
this.groupRoleAttribute = groupRoleAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchSubtree(boolean searchSubtree) {
|
||||||
|
// this.searchSubtree = searchSubtree;
|
||||||
|
this.searchScope = searchSubtree ?
|
||||||
|
SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConvertToUpperCase(boolean convertToUpperCase) {
|
||||||
|
this.convertToUpperCase = convertToUpperCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) {
|
||||||
|
this.initialDirContextFactory = initialDirContextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
if(initialDirContextFactory == null && groupSearchBase != null) {
|
||||||
|
throw new IllegalArgumentException("initialDirContextFactory is required for group role searches.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public abstract class AbstractLdapServerTestCase extends TestCase {
|
||||||
|
protected static final String ROOT_DN = "dc=acegisecurity,dc=org";
|
||||||
|
protected static final String PROVIDER_URL = "ldap://monkeymachine:389/"+ROOT_DN;
|
||||||
|
//protected static final String PROVIDER_URL = "ldap://localhost:10389/" + ROOT_DN;
|
||||||
|
protected static final String MANAGER_USER = "cn=manager," + ROOT_DN;
|
||||||
|
protected static final String MANAGER_PASSWORD = "acegisecurity";
|
||||||
|
|
||||||
|
|
||||||
|
// protected static final LdapTestServer server = new LdapTestServer();
|
||||||
|
|
||||||
|
protected AbstractLdapServerTestCase() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractLdapServerTestCase(String string) {
|
||||||
|
super(string);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
package org.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
import org.springframework.dao.DataAccessResourceFailureException;
|
||||||
|
import org.acegisecurity.BadCredentialsException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link InitialDirContextFactory}.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class InitialDirContextFactoryTests extends AbstractLdapServerTestCase {
|
||||||
|
|
||||||
|
public void testNonLdapUrlIsRejected() throws Exception {
|
||||||
|
DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory();
|
||||||
|
|
||||||
|
idf.setUrl("http://acegisecurity.org/dc=acegisecurity,dc=org");
|
||||||
|
|
||||||
|
try {
|
||||||
|
idf.afterPropertiesSet();
|
||||||
|
fail("Expected exception for non 'ldap://' URL");
|
||||||
|
} catch(IllegalArgumentException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConnectionFailure() throws Exception {
|
||||||
|
DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory();
|
||||||
|
// Use the wrong port
|
||||||
|
idf.setUrl("ldap://localhost:60389");
|
||||||
|
Hashtable env = new Hashtable();
|
||||||
|
env.put("com.sun.jndi.ldap.connect.timeout", "200");
|
||||||
|
idf.setExtraEnvVars(env);
|
||||||
|
idf.afterPropertiesSet();
|
||||||
|
try {
|
||||||
|
idf.newInitialDirContext();
|
||||||
|
fail("Connection succeeded unexpectedly");
|
||||||
|
} catch(DataAccessResourceFailureException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAnonymousBindSucceeds() throws Exception {
|
||||||
|
DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory();
|
||||||
|
idf.setUrl(PROVIDER_URL);
|
||||||
|
idf.afterPropertiesSet();
|
||||||
|
DirContext ctx = idf.newInitialDirContext();
|
||||||
|
// Connection pooling should be set by default for anon users.
|
||||||
|
assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool"));
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBindAsManagerSucceeds() throws Exception {
|
||||||
|
DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory();
|
||||||
|
idf.setUrl(PROVIDER_URL);
|
||||||
|
idf.setManagerPassword(MANAGER_PASSWORD);
|
||||||
|
idf.setManagerDn(MANAGER_USER);
|
||||||
|
idf.afterPropertiesSet();
|
||||||
|
DirContext ctx = idf.newInitialDirContext();
|
||||||
|
assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool"));
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInvalidPasswordCausesBadCredentialsException() throws Exception {
|
||||||
|
DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory();
|
||||||
|
idf.setUrl(PROVIDER_URL);
|
||||||
|
idf.setManagerDn(MANAGER_USER);
|
||||||
|
idf.setManagerPassword("wrongpassword");
|
||||||
|
idf.afterPropertiesSet();
|
||||||
|
try {
|
||||||
|
DirContext ctx = idf.newInitialDirContext();
|
||||||
|
fail("Authentication with wrong credentials should fail.");
|
||||||
|
} catch(BadCredentialsException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConnectionAsSpecificUserSucceeds() throws Exception {
|
||||||
|
DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory();
|
||||||
|
idf.setUrl(PROVIDER_URL);
|
||||||
|
idf.afterPropertiesSet();
|
||||||
|
DirContext ctx = idf.newInitialDirContext("uid=Bob,ou=people,dc=acegisecurity,dc=org",
|
||||||
|
"bobspassword");
|
||||||
|
// We don't want pooling for specific users.
|
||||||
|
assertNull(ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool"));
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEnvironment() {
|
||||||
|
DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory();
|
||||||
|
idf.setUrl("ldap://acegisecurity.org/");
|
||||||
|
|
||||||
|
// check basic env
|
||||||
|
Hashtable env = idf.getEnvironment();
|
||||||
|
assertEquals("com.sun.jndi.ldap.LdapCtxFactory", env.get(Context.INITIAL_CONTEXT_FACTORY));
|
||||||
|
assertEquals("ldap://acegisecurity.org/", env.get(Context.PROVIDER_URL));
|
||||||
|
assertEquals("simple",env.get(Context.SECURITY_AUTHENTICATION));
|
||||||
|
assertNull(env.get(Context.SECURITY_PRINCIPAL));
|
||||||
|
assertNull(env.get(Context.SECURITY_CREDENTIALS));
|
||||||
|
|
||||||
|
// Ctx factory.
|
||||||
|
idf.setInitialContextFactory("org.acegisecurity.NonExistentCtxFactory");
|
||||||
|
env = idf.getEnvironment();
|
||||||
|
assertEquals("org.acegisecurity.NonExistentCtxFactory", env.get(Context.INITIAL_CONTEXT_FACTORY));
|
||||||
|
|
||||||
|
// Auth type
|
||||||
|
idf.setAuthenticationType("myauthtype");
|
||||||
|
env = idf.getEnvironment();
|
||||||
|
assertEquals("myauthtype", env.get(Context.SECURITY_AUTHENTICATION));
|
||||||
|
|
||||||
|
// Check extra vars
|
||||||
|
Hashtable extraVars = new Hashtable();
|
||||||
|
extraVars.put("extravar", "extravarvalue");
|
||||||
|
idf.setExtraEnvVars(extraVars);
|
||||||
|
env = idf.getEnvironment();
|
||||||
|
assertEquals("extravarvalue", env.get("extravar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBaseDnIsParsedFromCorrectlyFromUrl() throws Exception {
|
||||||
|
DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory();
|
||||||
|
|
||||||
|
idf.setUrl("ldap://acegisecurity.org/dc=acegisecurity,dc=org");
|
||||||
|
idf.afterPropertiesSet();
|
||||||
|
assertEquals("dc=acegisecurity,dc=org", idf.getRootDn());
|
||||||
|
|
||||||
|
// Check with an empty root
|
||||||
|
idf = new DefaultInitialDirContextFactory();
|
||||||
|
idf.setUrl("ldap://acegisecurity.org/");
|
||||||
|
idf.afterPropertiesSet();
|
||||||
|
assertEquals("", idf.getRootDn());
|
||||||
|
|
||||||
|
// Empty root without trailing slash
|
||||||
|
idf = new DefaultInitialDirContextFactory();
|
||||||
|
idf.setUrl("ldap://acegisecurity.org");
|
||||||
|
idf.afterPropertiesSet();
|
||||||
|
assertEquals("", idf.getRootDn());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
import javax.naming.directory.BasicAttributes;
|
||||||
|
|
||||||
|
import org.acegisecurity.GrantedAuthority;
|
||||||
|
import org.acegisecurity.GrantedAuthorityImpl;
|
||||||
|
import org.acegisecurity.BadCredentialsException;
|
||||||
|
import org.acegisecurity.Authentication;
|
||||||
|
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.acegisecurity.providers.ldap.authenticator.FilterBasedLdapUserSearch;
|
||||||
|
import org.acegisecurity.providers.ldap.authenticator.BindAuthenticator;
|
||||||
|
import org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator;
|
||||||
|
import org.acegisecurity.userdetails.UserDetails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class LdapAuthenticationProviderTests extends AbstractLdapServerTestCase {
|
||||||
|
DefaultInitialDirContextFactory dirCtxFactory;
|
||||||
|
|
||||||
|
|
||||||
|
public LdapAuthenticationProviderTests(String string) {
|
||||||
|
super(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LdapAuthenticationProviderTests() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNormalUsage() throws Exception {
|
||||||
|
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider();
|
||||||
|
|
||||||
|
ldapProvider.setAuthenticator(new MockAuthenticator());
|
||||||
|
ldapProvider.setLdapAuthoritiesPopulator(new MockAuthoritiesPopulator());
|
||||||
|
ldapProvider.afterPropertiesSet();
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("bob","bobspassword");
|
||||||
|
UserDetails user = ldapProvider.retrieveUser("bob", token);
|
||||||
|
assertEquals(1, user.getAuthorities().length);
|
||||||
|
assertTrue(user.getAuthorities()[0].equals("ROLE_USER"));
|
||||||
|
ldapProvider.additionalAuthenticationChecks(user, token);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testIntegration() throws Exception {
|
||||||
|
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider();
|
||||||
|
|
||||||
|
// Connection information
|
||||||
|
DefaultInitialDirContextFactory dirCtxFactory = new DefaultInitialDirContextFactory();
|
||||||
|
dirCtxFactory.setUrl(PROVIDER_URL);
|
||||||
|
dirCtxFactory.setManagerDn(MANAGER_USER);
|
||||||
|
dirCtxFactory.setManagerPassword(MANAGER_PASSWORD);
|
||||||
|
dirCtxFactory.afterPropertiesSet();
|
||||||
|
BindAuthenticator authenticator = new BindAuthenticator();
|
||||||
|
//PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator();
|
||||||
|
authenticator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
//authenticator.setUserDnPattern("cn={0},ou=people");
|
||||||
|
|
||||||
|
FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch();
|
||||||
|
userSearch.setSearchBase("ou=people");
|
||||||
|
userSearch.setSearchFilter("(cn={0})");
|
||||||
|
userSearch.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
userSearch.afterPropertiesSet();
|
||||||
|
|
||||||
|
authenticator.setUserSearch(userSearch);
|
||||||
|
|
||||||
|
authenticator.afterPropertiesSet();
|
||||||
|
|
||||||
|
DefaultLdapAuthoritiesPopulator populator;
|
||||||
|
populator = new DefaultLdapAuthoritiesPopulator();
|
||||||
|
populator.setRolePrefix("ROLE_");
|
||||||
|
populator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
populator.setGroupSearchBase("ou=groups");
|
||||||
|
populator.afterPropertiesSet();
|
||||||
|
|
||||||
|
ldapProvider.setLdapAuthoritiesPopulator(populator);
|
||||||
|
ldapProvider.setAuthenticator(authenticator);
|
||||||
|
Authentication auth = ldapProvider.authenticate(new UsernamePasswordAuthenticationToken("Ben Alex","benspassword"));
|
||||||
|
assertEquals(2, auth.getAuthorities().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator {
|
||||||
|
|
||||||
|
public GrantedAuthority[] getGrantedAuthorities(String userDn, String dn, Attributes userAttributes) {
|
||||||
|
return new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_USER") };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockAuthenticator implements LdapAuthenticator {
|
||||||
|
Attributes userAttributes = new BasicAttributes("cn","bob");
|
||||||
|
|
||||||
|
public LdapUserDetails authenticate(String username, String password) {
|
||||||
|
if(username.equals("bob") && password.equals("bobspassword")) {
|
||||||
|
|
||||||
|
return new LdapUserDetails("cn=bob,ou=people,dc=acegisecurity,dc=org", userAttributes);
|
||||||
|
}
|
||||||
|
throw new BadCredentialsException("Authentication of Bob failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package org.acegisecurity.providers.ldap;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.apache.ldap.server.configuration.MutableServerStartupConfiguration;
|
||||||
|
import org.apache.ldap.server.jndi.ServerContextFactory;
|
||||||
|
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.NameAlreadyBoundException;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
import javax.naming.directory.BasicAttributes;
|
||||||
|
import javax.naming.directory.Attribute;
|
||||||
|
import javax.naming.directory.BasicAttribute;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class LdapTestServer {
|
||||||
|
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private DirContext serverContext;
|
||||||
|
|
||||||
|
//~ Constructors ================================================================
|
||||||
|
|
||||||
|
public LdapTestServer() {
|
||||||
|
startLdapServer();
|
||||||
|
createManagerUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
private void startLdapServer() {
|
||||||
|
ApplicationContext factory = new ClassPathXmlApplicationContext( "org/acegisecurity/providers/ldap/apacheds-context.xml");
|
||||||
|
MutableServerStartupConfiguration cfg = ( MutableServerStartupConfiguration ) factory.getBean( "configuration" );
|
||||||
|
ClassPathResource ldifDir = new ClassPathResource("org/acegisecurity/providers/ldap/ldif");
|
||||||
|
|
||||||
|
try {
|
||||||
|
cfg.setLdifDirectory(ldifDir.getFile());
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Failed to set LDIF directory for server");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties env = ( Properties ) factory.getBean( "environment" );
|
||||||
|
|
||||||
|
env.setProperty( Context.PROVIDER_URL, "dc=acegisecurity,dc=org" );
|
||||||
|
env.setProperty( Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName() );
|
||||||
|
env.putAll( cfg.toJndiEnvironment() );
|
||||||
|
|
||||||
|
try {
|
||||||
|
serverContext = new InitialDirContext( env );
|
||||||
|
} catch (NamingException e) {
|
||||||
|
System.err.println("Failed to start Apache DS");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createManagerUser() {
|
||||||
|
Attributes user = new BasicAttributes( "cn", "manager" , true );
|
||||||
|
user.put( "userPassword", "acegisecurity" );
|
||||||
|
Attribute objectClass = new BasicAttribute("objectClass");
|
||||||
|
user.put( objectClass );
|
||||||
|
objectClass.add( "top" );
|
||||||
|
objectClass.add( "person" );
|
||||||
|
objectClass.add( "organizationalPerson" );
|
||||||
|
objectClass.add( "inetOrgPerson" );
|
||||||
|
user.put( "sn", "Manager" );
|
||||||
|
user.put( "cn", "manager" );
|
||||||
|
try {
|
||||||
|
serverContext.createSubcontext("cn=manager", user );
|
||||||
|
} catch(NameAlreadyBoundException ignore) {
|
||||||
|
System.out.println("Manager user already exists.");
|
||||||
|
} catch (NamingException ne) {
|
||||||
|
System.err.println("Failed to create manager user.");
|
||||||
|
ne.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirContext getServerContext() {
|
||||||
|
return serverContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
new LdapTestServer();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package org.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory;
|
||||||
|
import org.acegisecurity.providers.ldap.LdapUserDetails;
|
||||||
|
import org.acegisecurity.providers.ldap.AbstractLdapServerTestCase;
|
||||||
|
import org.acegisecurity.BadCredentialsException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link BindAuthenticator}.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class BindAuthenticatorTests extends AbstractLdapServerTestCase {
|
||||||
|
|
||||||
|
private DefaultInitialDirContextFactory dirCtxFactory;
|
||||||
|
private BindAuthenticator authenticator;
|
||||||
|
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
// Connection information
|
||||||
|
dirCtxFactory = new DefaultInitialDirContextFactory();
|
||||||
|
dirCtxFactory.setUrl(PROVIDER_URL);
|
||||||
|
dirCtxFactory.afterPropertiesSet();
|
||||||
|
authenticator = new BindAuthenticator();
|
||||||
|
authenticator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUserDnPatternReturnsCorrectDn() throws Exception {
|
||||||
|
authenticator.setUserDnPattern("cn={0},ou=people");
|
||||||
|
assertEquals("cn=Joe,ou=people,"+ ROOT_DN, authenticator.getUserDn("Joe"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAuthenticationWithCorrectPasswordSucceeds() throws Exception {
|
||||||
|
authenticator.setUserDnPattern("uid={0},ou=people");
|
||||||
|
LdapUserDetails user = authenticator.authenticate("bob","bobspassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAuthenticationWithWrongPasswordFails() {
|
||||||
|
BindAuthenticator authenticator = new BindAuthenticator();
|
||||||
|
|
||||||
|
authenticator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
authenticator.setUserDnPattern("uid={0},ou=people");
|
||||||
|
|
||||||
|
try {
|
||||||
|
authenticator.authenticate("bob","wrongpassword");
|
||||||
|
fail("Shouldn't be able to bind with wrong password");
|
||||||
|
} catch(BadCredentialsException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAuthenticationWithUserSearch() throws Exception {
|
||||||
|
LdapUserDetails user = new LdapUserDetails("uid=bob,ou=people," + ROOT_DN, null);
|
||||||
|
authenticator.setUserSearch(new MockUserSearch(user));
|
||||||
|
authenticator.afterPropertiesSet();
|
||||||
|
authenticator.authenticate("bob","bobspassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Apache DS falls apart with unknown DNs.
|
||||||
|
//
|
||||||
|
// public void testAuthenticationWithInvalidUserNameFails() {
|
||||||
|
// BindAuthenticator authenticator = new BindAuthenticator();
|
||||||
|
//
|
||||||
|
// authenticator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
// authenticator.setUserDnPattern("cn={0},ou=people");
|
||||||
|
// try {
|
||||||
|
// authenticator.authenticate("Baz","bobspassword");
|
||||||
|
// fail("Shouldn't be able to bind with invalid username");
|
||||||
|
// } catch(BadCredentialsException expected) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package org.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.AbstractLdapServerTestCase;
|
||||||
|
import org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory;
|
||||||
|
import org.acegisecurity.providers.ldap.LdapUserDetails;
|
||||||
|
import org.acegisecurity.userdetails.UsernameNotFoundException;
|
||||||
|
import org.acegisecurity.BadCredentialsException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for FilterBasedLdapUserSearch.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class FilterBasedLdapUserSearchTests extends AbstractLdapServerTestCase {
|
||||||
|
private DefaultInitialDirContextFactory dirCtxFactory;
|
||||||
|
private FilterBasedLdapUserSearch locator;
|
||||||
|
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
dirCtxFactory = new DefaultInitialDirContextFactory();
|
||||||
|
dirCtxFactory.setUrl(PROVIDER_URL);
|
||||||
|
dirCtxFactory.setManagerDn(MANAGER_USER);
|
||||||
|
dirCtxFactory.setManagerPassword(MANAGER_PASSWORD);
|
||||||
|
dirCtxFactory.afterPropertiesSet();
|
||||||
|
locator = new FilterBasedLdapUserSearch();
|
||||||
|
locator.setSearchSubtree(false);
|
||||||
|
locator.setSearchTimeLimit(0);
|
||||||
|
locator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterBasedLdapUserSearchTests(String string) {
|
||||||
|
super(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterBasedLdapUserSearchTests() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBasicSearch() throws Exception {
|
||||||
|
locator.setSearchBase("ou=people");
|
||||||
|
locator.setSearchFilter("(uid={0})");
|
||||||
|
locator.afterPropertiesSet();
|
||||||
|
LdapUserDetails bob = locator.searchForUser("Bob");
|
||||||
|
assertEquals("uid=bob,ou=people,"+ROOT_DN, bob.getDn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSubTreeSearchSucceeds() throws Exception {
|
||||||
|
// Don't set the searchBase, so search from the root.
|
||||||
|
locator.setSearchFilter("(uid={0})");
|
||||||
|
locator.setSearchSubtree(true);
|
||||||
|
locator.afterPropertiesSet();
|
||||||
|
LdapUserDetails bob = locator.searchForUser("Bob");
|
||||||
|
assertEquals("uid=bob,ou=people,"+ROOT_DN, bob.getDn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSearchForInvalidUserFails() {
|
||||||
|
locator.setSearchBase("ou=people");
|
||||||
|
locator.setSearchFilter("(uid={0})");
|
||||||
|
|
||||||
|
try {
|
||||||
|
locator.searchForUser("Joe");
|
||||||
|
fail("Expected UsernameNotFoundException for non-existent user.");
|
||||||
|
} catch (UsernameNotFoundException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailsOnMultipleMatches() {
|
||||||
|
locator.setSearchBase("ou=people");
|
||||||
|
locator.setSearchFilter("(cn=*)");
|
||||||
|
|
||||||
|
try {
|
||||||
|
locator.searchForUser("Ignored");
|
||||||
|
fail("Expected exception for multiple search matches.");
|
||||||
|
} catch (BadCredentialsException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Try some funny business with filters. */
|
||||||
|
public void testExtraFilterPartToExcludeBob() {
|
||||||
|
locator.setSearchBase("ou=people");
|
||||||
|
locator.setSearchFilter("(&(cn=*)(!(uid={0})))");
|
||||||
|
|
||||||
|
// Search for bob, get back ben...
|
||||||
|
LdapUserDetails ben = locator.searchForUser("bob");
|
||||||
|
assertEquals("cn=Ben Alex,ou=people,"+ROOT_DN, ben.getDn());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.LdapUserDetails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class MockUserSearch implements LdapUserSearch {
|
||||||
|
LdapUserDetails user;
|
||||||
|
|
||||||
|
public MockUserSearch(LdapUserDetails user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LdapUserDetails searchForUser(String username) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.jmock.Mock;
|
||||||
|
import org.jmock.MockObjectTestCase;
|
||||||
|
import org.acegisecurity.providers.ldap.InitialDirContextFactory;
|
||||||
|
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.BasicAttributes;
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class PasswordComparisonAuthenticatorMockTests extends MockObjectTestCase {
|
||||||
|
|
||||||
|
public void testLdapCompareIsUsedWhenPasswordIsNotRetrieved() throws Exception {
|
||||||
|
Mock mockCtx = new Mock(DirContext.class);
|
||||||
|
|
||||||
|
PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator();
|
||||||
|
authenticator.setUserDnPattern("cn={0},ou=people");
|
||||||
|
authenticator.setInitialDirContextFactory(
|
||||||
|
new MockInitialDirContextFactory((DirContext)mockCtx.proxy(),
|
||||||
|
"dc=acegisecurity,dc=org"));
|
||||||
|
// Get the mock to return an empty attribute set
|
||||||
|
mockCtx.expects(atLeastOnce()).method("getNameInNamespace").will(returnValue("dc=acegisecurity,dc=org"));
|
||||||
|
mockCtx.expects(once()).method("getAttributes").with(eq("cn=Bob,ou=people"), NULL).will(returnValue(new BasicAttributes()));
|
||||||
|
// Setup a single return value (i.e. success)
|
||||||
|
Attributes searchResults = new BasicAttributes("", null);
|
||||||
|
mockCtx.expects(once()).method("search").with(eq("cn=Bob,ou=people"),
|
||||||
|
eq("(userPassword={0})"), NOT_NULL, NOT_NULL).will(returnValue(searchResults.getAll()));
|
||||||
|
mockCtx.expects(once()).method("close");
|
||||||
|
authenticator.authenticate("Bob", "bobspassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockInitialDirContextFactory implements InitialDirContextFactory {
|
||||||
|
DirContext ctx;
|
||||||
|
String baseDn;
|
||||||
|
|
||||||
|
public MockInitialDirContextFactory(DirContext ctx, String baseDn) {
|
||||||
|
this.baseDn = baseDn;
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirContext newInitialDirContext() {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirContext newInitialDirContext(String username, String password) {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRootDn() {
|
||||||
|
return baseDn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package org.acegisecurity.providers.ldap.authenticator;
|
||||||
|
|
||||||
|
import org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory;
|
||||||
|
import org.acegisecurity.providers.ldap.LdapUserDetails;
|
||||||
|
import org.acegisecurity.providers.ldap.AbstractLdapServerTestCase;
|
||||||
|
import org.acegisecurity.providers.encoding.PlaintextPasswordEncoder;
|
||||||
|
import org.acegisecurity.BadCredentialsException;
|
||||||
|
import org.acegisecurity.userdetails.UsernameNotFoundException;
|
||||||
|
|
||||||
|
import javax.naming.directory.BasicAttributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class PasswordComparisonAuthenticatorTests extends AbstractLdapServerTestCase {
|
||||||
|
private DefaultInitialDirContextFactory dirCtxFactory;
|
||||||
|
private PasswordComparisonAuthenticator authenticator;
|
||||||
|
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
// Connection information
|
||||||
|
dirCtxFactory = new DefaultInitialDirContextFactory();
|
||||||
|
dirCtxFactory.setUrl(PROVIDER_URL);
|
||||||
|
dirCtxFactory.setManagerDn(MANAGER_USER);
|
||||||
|
dirCtxFactory.setManagerPassword(MANAGER_PASSWORD);
|
||||||
|
dirCtxFactory.afterPropertiesSet();
|
||||||
|
authenticator = new PasswordComparisonAuthenticator();
|
||||||
|
authenticator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
authenticator.setUserDnPattern("uid={0},ou=people");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tearDown() {
|
||||||
|
// com.sun.jndi.ldap.LdapPoolManager.showStats(System.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLdapCompareSucceedsWithCorrectPassword() {
|
||||||
|
// Don't retrieve the password
|
||||||
|
authenticator.setUserAttributes(new String[] {"cn", "sn"});
|
||||||
|
// Bob has a plaintext password.
|
||||||
|
authenticator.setPasswordEncoder(new PlaintextPasswordEncoder());
|
||||||
|
authenticator.authenticate("Bob", "bobspassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLdapCompareSucceedsWithShaEncodedPassword() {
|
||||||
|
authenticator = new PasswordComparisonAuthenticator();
|
||||||
|
authenticator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
authenticator.setUserDnPattern("cn={0},ou=people");
|
||||||
|
// Don't retrieve the password
|
||||||
|
authenticator.setUserAttributes(new String[] {"cn", "sn"});
|
||||||
|
authenticator.authenticate("Ben Alex", "benspassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPasswordEncoderCantBeNull() {
|
||||||
|
try {
|
||||||
|
authenticator.setPasswordEncoder(null);
|
||||||
|
fail("Password encoder can't be null");
|
||||||
|
} catch(IllegalArgumentException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLdapPasswordCompareFailsWithWrongPassword() {
|
||||||
|
// Don't retrieve the password
|
||||||
|
authenticator.setUserAttributes(new String[] {"cn", "sn"});
|
||||||
|
|
||||||
|
try {
|
||||||
|
authenticator.authenticate("Bob", "wrongpassword");
|
||||||
|
fail("Authentication should fail with wrong password.");
|
||||||
|
} catch(BadCredentialsException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLocalPasswordComparisonSucceedsWithCorrectPassword() {
|
||||||
|
authenticator.authenticate("Bob", "bobspassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLocalCompareSucceedsWithShaEncodedPassword() {
|
||||||
|
authenticator = new PasswordComparisonAuthenticator();
|
||||||
|
authenticator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
authenticator.setUserDnPattern("cn={0},ou=people");
|
||||||
|
authenticator.authenticate("Ben Alex", "benspassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLocalPasswordComparisonFailsWithWrongPassword() {
|
||||||
|
try {
|
||||||
|
authenticator.authenticate("Bob", "wrongpassword");
|
||||||
|
fail("Authentication should fail with wrong password.");
|
||||||
|
} catch(BadCredentialsException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllAttributesAreRetrivedByDefault() {
|
||||||
|
LdapUserDetails user = authenticator.authenticate("Bob", "bobspassword");
|
||||||
|
System.out.println(user.getAttributes().toString());
|
||||||
|
assertEquals("User should have 5 attributes", 5, user.getAttributes().size());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testOnlySpecifiedAttributesAreRetrieved() throws Exception {
|
||||||
|
authenticator.setUserAttributes(new String[] {"cn", "sn"});
|
||||||
|
authenticator.setPasswordEncoder(new PlaintextPasswordEncoder());
|
||||||
|
LdapUserDetails user = authenticator.authenticate("Bob", "bobspassword");
|
||||||
|
assertEquals("Should have retrieved 2 attributes (cn, sn)",2, user.getAttributes().size());
|
||||||
|
assertEquals("Bob Hamilton", user.getAttributes().get("cn").get());
|
||||||
|
assertEquals("Hamilton", user.getAttributes().get("sn").get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUseOfDifferentPasswordAttribute() {
|
||||||
|
authenticator.setPasswordAttributeName("sn");
|
||||||
|
authenticator.authenticate("Bob", "Hamilton");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testWithUserSearch() {
|
||||||
|
LdapUserDetails user = new LdapUserDetails("uid=Bob,ou=people" + ROOT_DN,
|
||||||
|
new BasicAttributes("userPassword","bobspassword"));
|
||||||
|
authenticator.setUserDnPattern(null);
|
||||||
|
assertNull(authenticator.getUserDnPattern());
|
||||||
|
assertNull(authenticator.getUserDn("Bob"));
|
||||||
|
authenticator.setUserSearch(new MockUserSearch(user));
|
||||||
|
authenticator.authenticate("ShouldntBeUsed","bobspassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailedSearchGivesUserNotFoundException() throws Exception {
|
||||||
|
authenticator.setUserDnPattern(null);
|
||||||
|
authenticator.setUserSearch(new MockUserSearch(null));
|
||||||
|
authenticator.afterPropertiesSet();
|
||||||
|
|
||||||
|
try {
|
||||||
|
authenticator.authenticate("Joe","password");
|
||||||
|
fail("Expected exception on failed user search");
|
||||||
|
} catch (UsernameNotFoundException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package org.acegisecurity.providers.ldap.populator;
|
||||||
|
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
import javax.naming.directory.BasicAttributes;
|
||||||
|
import javax.naming.directory.BasicAttribute;
|
||||||
|
|
||||||
|
import org.acegisecurity.GrantedAuthority;
|
||||||
|
import org.acegisecurity.providers.ldap.AbstractLdapServerTestCase;
|
||||||
|
import org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke Taylor
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class DefaultLdapAuthoritiesPopulatorTests extends AbstractLdapServerTestCase {
|
||||||
|
private DefaultInitialDirContextFactory dirCtxFactory;
|
||||||
|
private DefaultLdapAuthoritiesPopulator populator;
|
||||||
|
|
||||||
|
public void setUp() {
|
||||||
|
dirCtxFactory = new DefaultInitialDirContextFactory();
|
||||||
|
dirCtxFactory.setUrl(PROVIDER_URL);
|
||||||
|
dirCtxFactory.setManagerDn(MANAGER_USER);
|
||||||
|
dirCtxFactory.setManagerPassword(MANAGER_PASSWORD);
|
||||||
|
|
||||||
|
populator = new DefaultLdapAuthoritiesPopulator();
|
||||||
|
populator.setRolePrefix("ROLE_");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCtxFactoryMustBeSetIfSearchBaseIsSet() throws Exception {
|
||||||
|
populator.setGroupSearchBase("");
|
||||||
|
|
||||||
|
try {
|
||||||
|
populator.afterPropertiesSet();
|
||||||
|
fail("expected exception.");
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUserAttributeMappingToRoles() {
|
||||||
|
populator.setUserRoleAttributes(new String[] {"userRole", "otherUserRole"});
|
||||||
|
populator.getUserRoleAttributes();
|
||||||
|
|
||||||
|
Attributes userAttrs = new BasicAttributes();
|
||||||
|
BasicAttribute attr = new BasicAttribute("userRole", "role1");
|
||||||
|
attr.add("role2");
|
||||||
|
userAttrs.put(attr);
|
||||||
|
attr = new BasicAttribute("otherUserRole", "role3");
|
||||||
|
attr.add("role2"); // duplicate
|
||||||
|
userAttrs.put(attr);
|
||||||
|
|
||||||
|
GrantedAuthority[] authorities = populator.getGrantedAuthorities("Ignored", "Ignored", userAttrs);
|
||||||
|
assertEquals("User should have three roles", 3, authorities.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGroupSearch() throws Exception {
|
||||||
|
populator.setInitialDirContextFactory(dirCtxFactory);
|
||||||
|
populator.setGroupSearchBase("ou=groups");
|
||||||
|
populator.setGroupRoleAttribute("ou");
|
||||||
|
populator.setSearchSubtree(true);
|
||||||
|
populator.setSearchSubtree(false);
|
||||||
|
populator.setConvertToUpperCase(true);
|
||||||
|
populator.setGroupSearchFilter("member={0}");
|
||||||
|
populator.afterPropertiesSet();
|
||||||
|
|
||||||
|
GrantedAuthority[] authorities = populator.getGrantedAuthorities("Ben", "cn=Ben Alex,ou=people,"+ROOT_DN, new BasicAttributes());
|
||||||
|
assertEquals("Should have 2 roles", 2, authorities.length);
|
||||||
|
Set roles = new HashSet();
|
||||||
|
roles.add(authorities[0].toString());
|
||||||
|
roles.add(authorities[1].toString());
|
||||||
|
assertTrue(roles.contains("ROLE_DEVELOPER"));
|
||||||
|
assertTrue(roles.contains("ROLE_MANAGER"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
|
||||||
|
"http://www.springframework.org/dtd/spring-beans.dtd">
|
||||||
|
|
||||||
|
<beans>
|
||||||
|
<!-- JNDI environment variable -->
|
||||||
|
<bean id="environment" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
|
||||||
|
<property name="properties">
|
||||||
|
<props>
|
||||||
|
<!--prop key="asn.1.berlib.provider">org.apache.ldap.common.berlib.asn1.SnickersProvider</prop -->
|
||||||
|
<!--prop key="asn.1.berlib.provider">org.apache.asn1new.ldap.TwixProvider</prop-->
|
||||||
|
<prop key="java.naming.security.authentication">simple</prop>
|
||||||
|
<prop key="java.naming.security.principal">uid=admin,ou=system</prop>
|
||||||
|
<prop key="java.naming.security.credentials">secret</prop>
|
||||||
|
<prop key="java.naming.ldap.attributes.binary">
|
||||||
|
photo personalSignature audio jpegPhoto javaSerializedData userPassword
|
||||||
|
userCertificate cACertificate authorityRevocationList certificateRevocationList
|
||||||
|
crossCertificatePair x500UniqueIdentifier krb5Key
|
||||||
|
</prop>
|
||||||
|
</props>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<!-- StartupConfiguration to start ApacheDS -->
|
||||||
|
<bean id="configuration" class="org.apache.ldap.server.configuration.MutableServerStartupConfiguration">
|
||||||
|
<property name="workingDirectory"><value>${java.io.tmpdir}/apache_ds</value></property>
|
||||||
|
<property name="allowAnonymousAccess"><value>true</value></property>
|
||||||
|
<property name="accessControlEnabled"><value>false</value></property>
|
||||||
|
<property name="ldapPort"><value>10389</value></property>
|
||||||
|
<property name="contextPartitionConfigurations">
|
||||||
|
<set>
|
||||||
|
<ref bean="acegiPartitionConfiguration"/>
|
||||||
|
</set>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<!-- Bootstrap schemas -->
|
||||||
|
<!-- <property name="bootstrapSchemas">
|
||||||
|
<set>
|
||||||
|
<bean class="org.apache.ldap.server.schema.bootstrap.AutofsSchema"/>
|
||||||
|
<bean class="org.apache.ldap.server.schema.bootstrap.CorbaSchema"/>
|
||||||
|
<bean class="org.apache.ldap.server.schema.bootstrap.CoreSchema"/>
|
||||||
|
|
||||||
|
|
||||||
|
</set>
|
||||||
|
</property>
|
||||||
|
-->
|
||||||
|
<!-- Interceptor configurations -->
|
||||||
|
<!--property name="interceptorConfigurations">
|
||||||
|
<list>
|
||||||
|
<bean class="org.apache.ldap.server.configuration.MutableInterceptorConfiguration">
|
||||||
|
<property name="name"><value>normalizationService</value></property>
|
||||||
|
<property name="interceptor">
|
||||||
|
<bean class="org.apache.ldap.server.normalization.NormalizationService" />
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
<bean class="org.apache.ldap.server.configuration.MutableInterceptorConfiguration">
|
||||||
|
<property name="name"><value>authenticationService</value></property>
|
||||||
|
<property name="interceptor">
|
||||||
|
<bean class="org.apache.ldap.server.authn.AuthenticationService" />
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
-->
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<!-- Additional ContextPartitionConfiguration -->
|
||||||
|
<bean id="acegiPartitionConfiguration" class="org.apache.ldap.server.configuration.MutableDirectoryPartitionConfiguration">
|
||||||
|
<property name="name"><value>acegisecurity</value></property>
|
||||||
|
<property name="suffix"><value>dc=acegisecurity,dc=org</value></property>
|
||||||
|
<property name="indexedAttributes">
|
||||||
|
<set>
|
||||||
|
<value>objectClass</value>
|
||||||
|
<value>ou</value>
|
||||||
|
<value>uid</value>
|
||||||
|
</set>
|
||||||
|
</property>
|
||||||
|
<property name="contextEntry">
|
||||||
|
<value>
|
||||||
|
objectClass: top
|
||||||
|
objectClass: domain
|
||||||
|
objectClass: extensibleObject
|
||||||
|
dc: apache
|
||||||
|
</value>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<!-- Custom editors required to launch ApacheDS -->
|
||||||
|
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
|
||||||
|
<property name="customEditors">
|
||||||
|
<map>
|
||||||
|
<entry key="javax.naming.directory.Attributes">
|
||||||
|
<bean class="org.apache.ldap.server.configuration.AttributesPropertyEditor"/>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
</beans>
|
|
@ -0,0 +1,54 @@
|
||||||
|
version: 1
|
||||||
|
dn: dc=acegisecurity,dc=org
|
||||||
|
objectClass: dcObject
|
||||||
|
objectClass: organization
|
||||||
|
dc: acegisecurity
|
||||||
|
description: Acegi Security (Test LDAP DIT)
|
||||||
|
o: Monkey Machine Ltd.
|
||||||
|
|
||||||
|
dn: ou=people,dc=acegisecurity,dc=org
|
||||||
|
objectClass: organizationalUnit
|
||||||
|
description: All people in organisation
|
||||||
|
ou: people
|
||||||
|
|
||||||
|
dn: cn=Ben Alex,ou=people,dc=acegisecurity,dc=org
|
||||||
|
objectClass: inetOrgPerson
|
||||||
|
objectClass: organizationalPerson
|
||||||
|
objectClass: person
|
||||||
|
objectClass: top
|
||||||
|
cn: Ben Alex
|
||||||
|
sn: Alex
|
||||||
|
uid: Ben
|
||||||
|
userPassword:: e3NoYX1uRkNlYldqeGZhTGJISEcxUWs1VVU0dHJidlE9
|
||||||
|
|
||||||
|
dn: uid=bob,ou=people,dc=acegisecurity,dc=org
|
||||||
|
objectClass: inetOrgPerson
|
||||||
|
objectClass: organizationalPerson
|
||||||
|
objectClass: person
|
||||||
|
objectClass: top
|
||||||
|
cn: Bob Hamilton
|
||||||
|
sn: Hamilton
|
||||||
|
uid: bob
|
||||||
|
userPassword:: Ym9ic3Bhc3N3b3Jk
|
||||||
|
|
||||||
|
dn: ou=groups,dc=acegisecurity,dc=org
|
||||||
|
objectClass: top
|
||||||
|
objectClass: organizationalUnit
|
||||||
|
ou: groups
|
||||||
|
|
||||||
|
dn: cn=developers,ou=groups,dc=acegisecurity,dc=org
|
||||||
|
objectClass: groupOfNames
|
||||||
|
objectClass: top
|
||||||
|
cn: developers
|
||||||
|
description: Acegi Security Developers
|
||||||
|
member: uid=bob,ou=people,dc=acegisecurity,dc=org
|
||||||
|
member: cn=Ben Alex,ou=people,dc=acegisecurity,dc=org
|
||||||
|
o: Acegi Security System for Spring
|
||||||
|
ou: developer
|
||||||
|
|
||||||
|
dn: cn=managers,ou=groups,dc=acegisecurity,dc=org
|
||||||
|
objectClass: groupOfNames
|
||||||
|
objectClass: top
|
||||||
|
cn: managers
|
||||||
|
member: cn=Ben Alex,ou=people,dc=acegisecurity,dc=org
|
||||||
|
ou: manager
|
Loading…
Reference in New Issue