8 changed files with 797 additions and 0 deletions
@ -0,0 +1,285 @@
@@ -0,0 +1,285 @@
|
||||
package org.acegisecurity.ldap; |
||||
|
||||
import org.acegisecurity.AcegiMessageSource; |
||||
import org.acegisecurity.BadCredentialsException; |
||||
import org.springframework.context.MessageSourceAware; |
||||
import org.springframework.context.MessageSource; |
||||
import org.springframework.context.support.MessageSourceAccessor; |
||||
import org.springframework.util.Assert; |
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
|
||||
import javax.naming.directory.DirContext; |
||||
import javax.naming.directory.InitialDirContext; |
||||
import javax.naming.Context; |
||||
import javax.naming.CommunicationException; |
||||
import javax.naming.NamingException; |
||||
import java.util.Map; |
||||
import java.util.StringTokenizer; |
||||
import java.util.Hashtable; |
||||
|
||||
/** |
||||
* 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 constructor argument |
||||
* <tt>providerUrl</tt>. This should be in the form |
||||
* <tt>ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org</tt>. The Sun JNDI
|
||||
* provider also supports lists of space-separated URLs, each of which will be tried |
||||
* in turn until a connection is obtained. |
||||
* </p> |
||||
* <p> |
||||
* To obtain an initial context, the 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 unless a manager login has been configured |
||||
* using the properties <tt>managerDn</tt> and <tt>managerPassword</tt>, in which case |
||||
* it will bind as the manager user. |
||||
* </p> |
||||
* <p> |
||||
* Connection pooling is enabled by default 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 LDAP connection pooling</a> |
||||
* |
||||
* @author Robert Sanders |
||||
* @author Luke Taylor |
||||
* @version $Id$ |
||||
* |
||||
*/ |
||||
public class DefaultInitialDirContextFactory implements InitialDirContextFactory, |
||||
MessageSourceAware { |
||||
|
||||
//~ Static fields/initializers =============================================
|
||||
|
||||
private static final Log logger = LogFactory.getLog(org.acegisecurity.ldap.DefaultInitialDirContextFactory.class); |
||||
|
||||
private static final String CONNECTION_POOL_KEY = "com.sun.jndi.ldap.connect.pool"; |
||||
|
||||
private static final String AUTH_TYPE_NONE = "none"; |
||||
|
||||
//~ Instance fields ========================================================
|
||||
|
||||
protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor(); |
||||
|
||||
/** |
||||
* The LDAP url of the server (and root context) to connect to. |
||||
*/ |
||||
private String providerUrl; |
||||
|
||||
/** |
||||
* 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 = null; |
||||
|
||||
/** |
||||
* 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 = "manager_password_not_set"; |
||||
|
||||
/** 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; |
||||
|
||||
//~ Constructors ===========================================================
|
||||
|
||||
public DefaultInitialDirContextFactory(String providerUrl) { |
||||
this.providerUrl = providerUrl; |
||||
|
||||
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied."); |
||||
|
||||
StringTokenizer st = new StringTokenizer(providerUrl); |
||||
|
||||
// Work out rootDn from the first URL and check that the other URLs (if any) match
|
||||
while(st.hasMoreTokens()) { |
||||
String url = st.nextToken(); |
||||
String urlRootDn = LdapUtils.parseRootDnFromUrl(url); |
||||
|
||||
org.acegisecurity.ldap.DefaultInitialDirContextFactory.logger.info(" URL '" + url +"', root DN is '" + urlRootDn + "'"); |
||||
|
||||
if(rootDn == null) { |
||||
rootDn = urlRootDn; |
||||
} else if (!rootDn.equals(urlRootDn)) { |
||||
throw new IllegalArgumentException("Root DNs must be the same when using multiple URLs"); |
||||
} |
||||
} |
||||
|
||||
// This doesn't necessarily hold for embedded servers.
|
||||
//Assert.isTrue(uri.getScheme().equals("ldap"), "Ldap URL must start with 'ldap://'");
|
||||
} |
||||
|
||||
|
||||
//~ Methods ================================================================
|
||||
|
||||
/** |
||||
* Connects anonymously unless a manager user has been specified, in which case |
||||
* it will bind as the manager. |
||||
* |
||||
* @return the resulting context object. |
||||
*/ |
||||
public DirContext newInitialDirContext() { |
||||
|
||||
if (managerDn != null) { |
||||
return newInitialDirContext(managerDn, managerPassword); |
||||
} |
||||
|
||||
Hashtable env = getEnvironment(); |
||||
env.put(Context.SECURITY_AUTHENTICATION, org.acegisecurity.ldap.DefaultInitialDirContextFactory.AUTH_TYPE_NONE); |
||||
|
||||
return connect(env); |
||||
} |
||||
|
||||
public DirContext newInitialDirContext(String username, String password) { |
||||
Hashtable env = getEnvironment(); |
||||
|
||||
// Don't pool connections for individual users
|
||||
if (!username.equals(managerDn)) { |
||||
env.remove(org.acegisecurity.ldap.DefaultInitialDirContextFactory.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.SECURITY_AUTHENTICATION, authenticationType); |
||||
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); |
||||
env.put(Context.PROVIDER_URL, providerUrl); |
||||
|
||||
if (useConnectionPool) { |
||||
env.put(org.acegisecurity.ldap.DefaultInitialDirContextFactory.CONNECTION_POOL_KEY, "true"); |
||||
} |
||||
|
||||
if ((extraEnvVars != null) && (extraEnvVars.size() > 0)) { |
||||
env.putAll(extraEnvVars); |
||||
} |
||||
|
||||
return env; |
||||
} |
||||
|
||||
private InitialDirContext connect(Hashtable env) { |
||||
|
||||
if (org.acegisecurity.ldap.DefaultInitialDirContextFactory.logger.isDebugEnabled()) { |
||||
Hashtable envClone = (Hashtable)env.clone(); |
||||
|
||||
if (envClone.containsKey(Context.SECURITY_CREDENTIALS)) { |
||||
envClone.put(Context.SECURITY_CREDENTIALS, "******"); |
||||
} |
||||
|
||||
org.acegisecurity.ldap.DefaultInitialDirContextFactory.logger.debug("Creating InitialDirContext with environment " + envClone); |
||||
} |
||||
|
||||
try { |
||||
return new InitialDirContext(env); |
||||
|
||||
} catch(CommunicationException ce) { |
||||
throw new LdapDataAccessException(messages.getMessage( |
||||
"DefaultIntitalDirContextFactory.communicationFailure", |
||||
"Unable to connect to LDAP server"), ce); |
||||
} catch(javax.naming.AuthenticationException ae) { |
||||
throw new BadCredentialsException(messages.getMessage( |
||||
"DefaultIntitalDirContextFactory.badCredentials", |
||||
"Bad credentials"), ae); |
||||
} catch (NamingException nx) { |
||||
throw new LdapDataAccessException(messages.getMessage( |
||||
"DefaultIntitalDirContextFactory.unexpectedException", |
||||
"Failed to obtain InitialDirContext due to unexpected exception"), nx); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 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, "LDAP Authentication type must not be empty or null"); |
||||
this.authenticationType = authenticationType; |
||||
} |
||||
|
||||
public void setInitialContextFactory(String initialContextFactory) { |
||||
Assert.hasLength(initialContextFactory, "Initial context factory name cannot be empty or null"); |
||||
this.initialContextFactory = initialContextFactory; |
||||
} |
||||
|
||||
/** |
||||
* @param managerDn The name of the "manager" user for default authentication. |
||||
*/ |
||||
public void setManagerDn(String managerDn) { |
||||
Assert.hasLength(managerDn, "Manager user name cannot be empty or null."); |
||||
this.managerDn = managerDn; |
||||
} |
||||
|
||||
/** |
||||
* @param managerPassword The "manager" user's password. |
||||
*/ |
||||
public void setManagerPassword(String managerPassword) { |
||||
Assert.hasLength(managerPassword, "Manager password must not be empty or null."); |
||||
this.managerPassword = managerPassword; |
||||
} |
||||
|
||||
/** |
||||
* @param extraEnvVars extra environment variables to be added at config time. |
||||
*/ |
||||
public void setExtraEnvVars(Map extraEnvVars) { |
||||
Assert.notNull(extraEnvVars, "Extra environment map cannot be null."); |
||||
this.extraEnvVars = extraEnvVars; |
||||
} |
||||
|
||||
public void setMessageSource(MessageSource messageSource) { |
||||
this.messages = new MessageSourceAccessor(messageSource); |
||||
} |
||||
|
||||
/** |
||||
* Connection pooling is enabled by default for anonymous or "manager" |
||||
* connections when using the default Sun provider. To disable all |
||||
* connection pooling, set this property to false. |
||||
* |
||||
* @param useConnectionPool whether to pool connections for non-specific users. |
||||
*/ |
||||
public void setUseConnectionPool(boolean useConnectionPool) { |
||||
this.useConnectionPool = useConnectionPool; |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
package org.acegisecurity.ldap; |
||||
|
||||
import javax.naming.directory.DirContext; |
||||
|
||||
/** |
||||
* Access point for obtaining LDAP contexts. |
||||
* |
||||
* @see org.acegisecurity.ldap.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,36 @@
@@ -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.ldap; |
||||
|
||||
import org.acegisecurity.AuthenticationServiceException; |
||||
|
||||
/** |
||||
* Used to wrap unexpected NamingExceptions while accessing the LDAP server |
||||
* or for other LDAP-related data problems such as data we can't handle. |
||||
* |
||||
* @author Luke Taylor |
||||
* @version $Id$ |
||||
*/ |
||||
public class LdapDataAccessException extends AuthenticationServiceException { |
||||
|
||||
public LdapDataAccessException(String msg) { |
||||
super(msg); |
||||
} |
||||
|
||||
public LdapDataAccessException(String msg, Throwable ex) { |
||||
super(msg, ex); |
||||
} |
||||
} |
||||
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
/* 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.ldap; |
||||
|
||||
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 LdapUserInfo { |
||||
|
||||
//~ 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 LdapUserInfo(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,41 @@
@@ -0,0 +1,41 @@
|
||||
/* 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.ldap; |
||||
|
||||
/** |
||||
* 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 LdapUserInfo object containing the user's full DN and requested attributes. |
||||
* TODO: Need to optionally supply required attributes here for the search. |
||||
*/ |
||||
LdapUserInfo searchForUser(String username); |
||||
|
||||
} |
||||
@ -0,0 +1,149 @@
@@ -0,0 +1,149 @@
|
||||
/* 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.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; |
||||
import java.net.URI; |
||||
import java.net.URISyntaxException; |
||||
|
||||
/** |
||||
* 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); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Parses the supplied LDAP URL. |
||||
* @param url the URL (e.g. <tt>ldap://monkeymachine:11389/dc=acegisecurity,dc=org</tt>).
|
||||
* @return the URI object created from the URL |
||||
* @throws IllegalArgumentException if the URL is null, empty or the URI syntax is invalid. |
||||
*/ |
||||
public static URI parseLdapUrl(String url) { |
||||
Assert.hasLength(url); |
||||
|
||||
try { |
||||
return new URI(url); |
||||
} catch (URISyntaxException e) { |
||||
IllegalArgumentException iae = new IllegalArgumentException("Unable to parse url: " + url); |
||||
iae.initCause(e); |
||||
throw iae; |
||||
} |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
/** |
||||
* Works out the root DN for an LDAP URL. |
||||
* <p> |
||||
* For example, the URL <tt>ldap://monkeymachine:11389/dc=acegisecurity,dc=org</tt>
|
||||
* has the root DN "dc=acegisecurity,dc=org". |
||||
* |
||||
* |
||||
* @param url the LDAP URL |
||||
* @return the root DN |
||||
*/ |
||||
public static String parseRootDnFromUrl(String url) { |
||||
Assert.hasLength(url); |
||||
|
||||
String urlRootDn = null; |
||||
|
||||
if (url.startsWith("ldap:") || url.startsWith("ldaps:")) { |
||||
|
||||
URI uri = parseLdapUrl(url); |
||||
|
||||
urlRootDn = uri.getPath(); |
||||
|
||||
} else { |
||||
// Assume it's an embedded server
|
||||
urlRootDn = url; |
||||
} |
||||
|
||||
if (urlRootDn.startsWith("/")) { |
||||
urlRootDn = urlRootDn.substring(1); |
||||
} |
||||
|
||||
return urlRootDn; |
||||
} |
||||
} |
||||
@ -0,0 +1,181 @@
@@ -0,0 +1,181 @@
|
||||
/* 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.ldap.search; |
||||
|
||||
import org.acegisecurity.userdetails.UsernameNotFoundException; |
||||
import org.acegisecurity.BadCredentialsException; |
||||
import org.acegisecurity.ldap.LdapUserSearch; |
||||
import org.acegisecurity.ldap.LdapUtils; |
||||
import org.acegisecurity.ldap.InitialDirContextFactory; |
||||
import org.acegisecurity.ldap.LdapUserInfo; |
||||
import org.acegisecurity.ldap.LdapDataAccessException; |
||||
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 ================================================================
|
||||
|
||||
public FilterBasedLdapUserSearch(String searchBase, |
||||
String searchFilter, |
||||
InitialDirContextFactory initialDirContextFactory) { |
||||
Assert.notNull(initialDirContextFactory, "initialDirContextFactory must not be null"); |
||||
Assert.notNull(searchFilter, "searchFilter must not be null."); |
||||
Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable)."); |
||||
|
||||
this.searchFilter = searchFilter; |
||||
this.initialDirContextFactory = initialDirContextFactory; |
||||
this.searchBase = searchBase; |
||||
|
||||
if(searchBase.length() == 0) { |
||||
logger.info("SearchBase not set. Searches will be performed from the root: " + |
||||
initialDirContextFactory.getRootDn()); |
||||
} |
||||
} |
||||
|
||||
//~ Methods ================================================================
|
||||
|
||||
/** |
||||
* Return the LdapUserInfo containing the user's information, or null if |
||||
* no SearchResult is found. |
||||
* |
||||
* @param username the username to search for. |
||||
*/ |
||||
public LdapUserInfo searchForUser(String username) { |
||||
DirContext ctx = initialDirContextFactory.newInitialDirContext(); |
||||
SearchControls ctls = new SearchControls(); |
||||
ctls.setTimeLimit( searchTimeLimit ); |
||||
ctls.setSearchScope( searchScope ); |
||||
|
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Searching for user '" + username + "', in context " + ctx + |
||||
", with user search " + this.toString()); |
||||
} |
||||
|
||||
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 LdapUserInfo(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 setSearchSubtree(boolean searchSubtree) { |
||||
// this.searchSubtree = searchSubtree;
|
||||
this.searchScope = searchSubtree ? |
||||
SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE; |
||||
} |
||||
|
||||
public void setSearchTimeLimit(int searchTimeLimit) { |
||||
this.searchTimeLimit = searchTimeLimit; |
||||
} |
||||
|
||||
public String toString() { |
||||
StringBuffer sb = new StringBuffer(); |
||||
|
||||
sb.append("[ searchFilter: '").append(searchFilter).append("', "); |
||||
sb.append("searchBase: '").append(searchBase).append("'"); |
||||
sb.append(", scope: ").append(searchScope == |
||||
SearchControls.SUBTREE_SCOPE ? "subtree" : "single-level, "); |
||||
sb.append("searchTimeLimit: ").append(searchTimeLimit).append(" ]"); |
||||
|
||||
return sb.toString(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue