diff --git a/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/LdapPasswordAuthenticationDao.java b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/LdapPasswordAuthenticationDao.java index e9cb085330..2b13fe20a2 100644 --- a/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/LdapPasswordAuthenticationDao.java +++ b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/LdapPasswordAuthenticationDao.java @@ -1,918 +1,314 @@ -/* 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 net.sf.acegisecurity.providers.dao.ldap; -import net.sf.acegisecurity.BadCredentialsException; -import net.sf.acegisecurity.GrantedAuthority; -import net.sf.acegisecurity.GrantedAuthorityImpl; -import net.sf.acegisecurity.UserDetails; -import net.sf.acegisecurity.providers.dao.PasswordAuthenticationDao; -import net.sf.acegisecurity.providers.dao.User; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.dao.DataAccessException; -import org.springframework.dao.DataAccessResourceFailureException; - import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; -import java.util.Hashtable; -import java.util.List; import javax.naming.AuthenticationException; -import javax.naming.CommunicationException; -import javax.naming.Context; +import javax.naming.Name; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; -import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; -import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; -/** -*

-* LdapPasswordAuthenticationDao allows you to authenticate user's against LDAP Directories via JNDI. -* LDAP administrators have a wide variety of options available to them when configuring a server, -* so the LdapPasswordAuthenticationDao has a wide variety of ways that it can be configured.

-* -* -*

-* Currently LdapPasswordAuthenticationDao authenticates a username/password pair by -* 'logging in to' the LDAP server via a JNDI bind() operation. -* There is some flexibility in that multiple userContexts can be set; the -* LdapPasswordAuthenticationDao will attempt to bind() against each until either a bind() -* operation succeeds or all userContexts have been tried.

-* -*

-* LdapPasswordAuthenticationDao offers 3 modes for determining the roles assigned to a user -* (these can be used in combination).

-* -* -*

-* If the both the userRolesAttributes method and the roleContexts search method are used, -* and if both return results, then the final list of roles will be determined by combining the two results. -*

-*

-* One final operation is performed before returning the list of GrantedAuthority -* objects associated with the user: if the the upperCaseRoleNames property is set to -* true the user's role names are capitalized; then the values of the rolePrefix and roleSuffix -* are used to wrap any role names. -*

-*

-* At this point a few examples will probably help clear up the confusion -* that the abstract description above may have created. -* Unless otherwise noted, all examples will use the following base set of assumptions: -* An LDAP server reachable at the url ldap://ldap.mycompany.com:389/ -* and a rootContext of dc=mycompany,dc=com. The following would be you AuthenticationProvider: -*

-*     <bean id="authenticationProvider" class="net.sf.acegisecurity.providers.dao.PasswordDaoAuthenticationProvider">
-*         <property name="passwordAuthenticationDao"><ref local="ldapDaoImpl"/></property>
-*     </bean>
-* 
-*

-* -*

-* First example: your users are stored under the rootContext as cn=USERNAME,ou=Users; -* user objects have the attribute memberOf which contains the names of any roles they -* have been granted. You would use the following bean configuration: -*

-*     <bean id="ldapDaoImpl" class="net.sf.acegisecurity.providers.dao.ldap.LdapPasswordAuthenticationDao">
-*         <property name="url"><value>ldap://ldap.mycompany.com:389/</value></property>
-*         <property name="rootContext"><value>dc=mycompany,dc=com</value></property>
-*         <property name="userContext"><alue>cn={0},ou=Users,dc=mycompany,dc=com</value></property>
-*         <property name="userRolesAttribute"><value>memberOf</value></property>
-*     </bean>
-* 
-*

-* -*

-* Second example: users are stored under the rootContext as uid=USERNAME,ou=Users; -* user object have no role information. Groups (aka roles) are stored as objects -* under the context ou=Groups and have an attribute memberUid which contains the -* full distinguished name of the user. You would use the following bean configuration: -*

-*     <bean id="ldapDaoImpl" class="net.sf.acegisecurity.providers.dao.ldap.LdapPasswordAuthenticationDao">
-*         <property name="url"><value>ldap://ldap.mycompany.com:389/</value></property> 
-*         <property name="rootContext"><value>dc=mycompany,dc=com</value></property> 
-*         <!-- here {0} is the username -->
-*         <property name="userContext"><value>uid={0},ou=Users,dc=mycompany,dc=com</value></property>
-*         <property name="roleContext"><value>ou=Groups</value></property> 
-*         <!-- here {0} is the distinguished name (which would be uid=USERNAME,ou=Users,dc=mycompany,cd=com
-*           and {1} is the username. -->
-*         <property name="roleAttributesSearchFilter"><value>(memberUid={0})</value></property> 
-*         <property name="roleNameAttribute"><value>memberUid</value></property> 
-*     </bean>
-* 
-*

-* -*

-* Third example: under the rootContext your users are stored as uid=USERNAME,ou=Users. -* You don't care about the roles stored in the LDAP, all you want to know is if the user -* can login via LDAP. You would use the following bean configuration: -*

-*     <bean id="ldapDaoImpl" class="net.sf.acegisecurity.providers.dao.ldap.LdapPasswordAuthenticationDao">
-*         <property name="url"><value>ldap://ldap.mycompany.com:389/</value></property> 
-*         <property name="rootContext"><value>dc=mycompany,dc=com</value></property> 
-*         <property name="userContext"><alue>cn={0},ou=Users,dc=mycompany,dc=com</value></property> 
-*         <property name="defaultRolename"><value>USER</value></property> 
-*     </bean>
-* 
-*

-* -*

-* Forth example (something more complex): under the rootContext your users are stored in to seperate subContexts. -* Your internal users are under uid=USERNAME,ou=Users; you also have client logins stored -* under the context uid=USERNAME,ou=Clients. For internal users role information is stored -* under the context ou=Groups and have an attribute memberUid which contains the -* full distinguished name of the user. For clients, role information is stored as an attribute -* memberOf as part of their user object. You could split the definitions up into two separate -* LdapPasswordAuthenticationDao beans, but you could also use: -*

-*     <bean id="ldapDaoImpl" class="net.sf.acegisecurity.providers.dao.ldap.LdapPasswordAuthenticationDao">
-*         <property name="url"><value>ldap://ldap.mycompany.com:389/</value></property> 
-*         <property name="rootContext"><value>dc=mycompany,dc=com</value></property> 
-*         <!-- here {0} is the username -->
-*         <property name="userContexts">
-*           <list>
-*             <value>uid={0},ou=Users,dc=mycompany,dc=com</value>
-*             <value>uid={0},ou=Clients,dc=mycompany,dc=com</value>
-*           </list>
-*         </property>
-*         <property name="userRolesAttribute"><value>memberOf</value></property> 
-*         <property name="roleContext"><value>ou=Groups</value></property> 
-*         <!-- here {0} is the distinguished name (which would be uid=USERNAME,ou=Users,dc=mycompany,cd=com
-*           and {1} is the username. -->
-*         <property name="roleAttributesSearchFilter"><value>(memberUid={0})</value></property> 
-*         <property name="roleNameAttributes"><value>memberUid</value></property> 
-*     </bean>
-* 
-*

-* -* @author Karel Miarka -* @author Daniel Miller -* @author Robert Sanders -*/ -public class LdapPasswordAuthenticationDao implements PasswordAuthenticationDao { - - /** InnerClass used to keep context variable together. */ - private class UserContext { - public DirContext dirContext; - public String userPrincipal; - - /** - * Get the attribute(s) to match when searching for the user object. This - * implementation returns a "distinguishedName" attribute with the value - * returned by getUserPrincipal(username). A subclass may - * customize this behavior by overriding getUserPrincipal - * and/or getUsernameAttributes. - * - * @param username - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - public Attributes getUsernameAttributes() { - Attributes matchAttrs = new BasicAttributes(true); // ignore case - matchAttrs.put(new BasicAttribute("distinguishedName", userPrincipal)); - return matchAttrs; - } - } - - - public static final String BAD_CREDENTIALS_EXCEPTION_MESSAGE = "Invalid username, password or context"; - - private static final transient Log log = LogFactory - .getLog(LdapPasswordAuthenticationDao.class); - - /** Type of authentication within LDAP; default is simple. */ - private String authenticationType = "simple"; - - /** If set to a non-null value, and a user can be bound to a LDAP Conext, - * but no role information is found then this role is automatically added. - * If null (the default) then a BadCredentialsException is thrown - * - *

For example; if you have an LDAP directory with no role information - * stored, you might simply want to give any user who can login a role of "USER".

- */ - private String defaultRole = null; - - /** The INITIAL_CONTEXT_FACTORY used to create the JNDI Factory. - * Default is "com.sun.jndi.ldap.LdapCtxFactory"; you should not - * need to set this unless you have unusual needs. - **/ - private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory"; - - /** Internal variable, concatenation */ - private String providerUrl; - - /** Used to build LDAP Search Filter for finding roles (in the roleContexts) - * pointing to a user. Uses MessageFormat like tokens; {0} is the - * user's DistiguishedName, {1} is the user's username. - * For more information on syntax see - * javax.naming.directory.DirContext.search(), or RFC 2254. - * - *

Example: if each group has an attribute 'memberUid' with values being - * the usernames of the user's in that group, then the value of this property - * would be (memberUid={1})

- **/ - private String roleAttributesSearchFilter; - - /** Contexts to search for role's (which point to the user id). - *

Example, if you have a Groups object containing Groups of users then - * the expression: ou=Groups,dc=mycompany,dc=com might be used; - * alternatively, if rootContext="dc=mycompany,dc=com" then simply use "ou=Groups" here. - **/ - private String[] roleContexts; - - /** Attribute(s) of any role object returned from the roleContexts to use as role-names. - * Warning: if you do role lookups using the roleContexts and - * roleAttributesSearchFilter then you need to set roleNameAttributes or ALL attributes - * will be returned. - * - **/ - private String[] roleNameAttributes; - - /** Prefix to be associated with any roles found for a user, - * defaults to an empty string. - * Older versions of this class used "ROLE_" for this value. */ - private String rolePrefix = ""; - - /** Suffix to be associated with any roles found for a user, - * defaults to an empty string. */ - private String roleSuffix = ""; - - /** Root context of the LDAP Connection, if any is needed. - *

Example: dc=mycompany,dc=com

- *

Note: It is usually preferable to add this data as part of the - * userContexts and/or roleContexts attributes.

- **/ - private String rootContext = ""; - - /** If true then all role name values returned from the directory - * will be converted to uppercase. - */ - private boolean upperCaseRoleNames = false; - - /** - * LDAP URL (without the port) of the LDAP server to connect to; example - * ldap://dir.mycompany.com:389/ (port 389 is the standard LDAP port). - */ - private String url; - - /** One or more LDAP Contexts which contain user account information, use the - * MessageFormat key {0} to denote location where the user's username should - * be inserted into the expression to create a DistiguishedName. - *

Example:

cn={0},ou=Users,dc=mycompnay,dc=com

- *

Alternatively, if you had set rootContext="dc=mycompany,dc=com" then - * the first example would be rewritten as cn={0},ou=Users.

- **/ - private MessageFormat[] userContexts; - - /** Name(s) of the attribute(s) for a user account object - * contaning role names assigned to the user. Leave unset if there are none. - * Consult your LDAP server administrator to determine these value(s). - * - **/ - private String[] userRolesAttributes; - - /** - * - * @param results Result of searching on of the roleContexts for matches against the current user. - * @param roles List of roles the user has already been assigned. - * @throws NamingException - */ - protected void addAnyRolesFound(NamingEnumeration results, Collection roles) throws NamingException { - while (results.hasMore()) { - SearchResult result = (SearchResult)results.next(); - Attributes attrs = result.getAttributes(); - if (attrs == null) { - continue; - } - // Here we loop over the attributes returned in the SearchResult - // TODO replace with Utility method call: - NamingEnumeration e = attrs.getAll(); - while (e.hasMore()) { - Attribute a = (Attribute)e.next(); - for (int i = 0; i < a.size(); i++) { - roles.add( (String)a.get(i) ); - } - } - } - } - - /** - * @return Returns the defaultRole. - */ - public String getDefaultRole() { - return defaultRole; - } - - /** - * Get an array GrantedAuthorities given the list of roles - * obtained from the LDAP context. Delegates to - * getGrantedAuthority(String ldapRole). This function may - * be overridden in a subclass. - * - * @param ldapRoles - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - protected GrantedAuthority[] getGrantedAuthorities(String[] ldapRoles) { - GrantedAuthority[] grantedAuthorities = new GrantedAuthority[ldapRoles.length]; - - for (int i = 0; i < ldapRoles.length; i++) { - grantedAuthorities[i] = getGrantedAuthority(ldapRoles[i]); - } - - return grantedAuthorities; - } - - /** - * Get a GrantedAuthority given a role obtained from the LDAP - * context. If found in the LDAP role, the following characters are - * converted to underscore: ',' (comma), '=' (equals), ' ' (space) This - * function may be overridden in a subclass. - * - * @param ldapRole - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - protected GrantedAuthority getGrantedAuthority(String ldapRole) { - String roleName = rolePrefix + ldapRole.toUpperCase() + roleSuffix; - if (upperCaseRoleNames) { - roleName = roleName.toUpperCase(); - } - GrantedAuthority ga = new GrantedAuthorityImpl( roleName.replaceAll("[,=\\s]", "_") ); - - if (log.isDebugEnabled()) { - log.debug("GrantedAuthority: " + ga); - } - - return ga; - } - /* - public void testGetGrantedAuthorityString() { - LdapPasswordAuthenticationDao uut = new LdapPasswordAuthenticationDao(); - String[] test = { - "ROLE ABC DEF", "ROLE ABC,DEF", "ROLE ABC=DEF", "ROLE ABC_DEF", - "ROLE,ABC DEF", "ROLE,ABC,DEF", "ROLE,ABC=DEF", "ROLE,ABC_DEF", - "ROLE=ABC DEF", "ROLE=ABC,DEF", "ROLE=ABC=DEF", "ROLE=ABC_DEF", - "ROLE_ABC DEF", "ROLE_ABC,DEF", "ROLE_ABC=DEF", "ROLE_ABC_DEF", - }; - final String expected = "ROLE_ABC_DEF"; - - for (int i = 0; i < test.length; i++) { - assertEquals("Unexpected granted authority name.", expected, - uut.getGrantedAuthority(test[i]).getAuthority()); - } - } - */ - - /** - * @return The InitialContextFactory for creating the root JNDI context; defaults to "com.sun.jndi.ldap.LdapCtxFactory" - */ - public String getInitialContextFactory() { - return initialContextFactory; - } - - // ~ Methods - // ================================================================ - - /** - * Given a password, construct the Hashtable of JNDI values for a bind attempt. - */ - protected Hashtable getJdniEnvironment(String password) { - Hashtable env = new Hashtable(11); - env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); - env.put(Context.PROVIDER_URL, getProviderURL()); - env.put(Context.SECURITY_AUTHENTICATION, authenticationType); - env.put(Context.SECURITY_CREDENTIALS, password); - return env; - } - - /** - * @return The full "Provuder" URL for the LDAP source; it should look - * something like: ldap://www.mycompany.com:389/ - */ - public synchronized String getProviderURL() { - if (null == this.providerUrl) { - StringBuffer providerUrl = new StringBuffer( this.url ); - if (!this.url.endsWith("/")) { - providerUrl.append("/"); - } - providerUrl.append(this.rootContext); - this.providerUrl = providerUrl.toString(); - } - return this.providerUrl; - } - - /** - * @return Returns the roleUserAttributes. - */ - public String getRoleAttributesSearchFilter() { - return roleAttributesSearchFilter; - } - - - /** - * @return Array of MessageFormat String's for Contexts that store role information for users. - */ - public String[] getRoleContexts() { - return roleContexts; - } - - /** - * @return Returns the roleNameAttributes. - */ - public String[] getRoleNameAttributes() { - return roleNameAttributes; - } - - /** - * @return Returns the rolePrefix. - */ - public String getRolePrefix() { - return rolePrefix; - } - - - protected Collection getRolesFromRoleSearch(UserContext userContext, String username, String[] roleAttributes) { - if ((null == roleContexts) || (roleContexts.length == 0)) { - return null; - } - String[] searchFilterVars = new String[] {userContext.userPrincipal, username}; - - SearchControls controls = new SearchControls(); - controls.setSearchScope(SearchControls.SUBTREE_SCOPE); - controls.setReturningAttributes(roleAttributes); - - List roles = new ArrayList(); - for (int i = 0; i < roleContexts.length; i++) { - try { - NamingEnumeration results = userContext.dirContext.search( - roleContexts[i], roleAttributesSearchFilter, searchFilterVars, controls); - addAnyRolesFound(results, roles); - } catch (NamingException e) { - if (log.isInfoEnabled()) { - log.info("Unable to find user-role match in context = " + roleContexts[i], e); - } - } - } - return roles; - } - - /** - * Looksup any roleAttributes associated with the user's DN within the DirContext. - * - * @param userContext - * UserContext Object containing DirContext in which to operate, and the user's DistinguishedName. - * @param roleAttributes - * Names of all attributes to search for role name information. - * @return Collection of roles granted to the user within the JNDI Context. - * @throws NamingException - */ - protected Collection getRolesFromUserContext(UserContext userContext, String[] roleAttributes) - throws NamingException { - List roles = new ArrayList(); - if (roleAttributes != null) { - if (log.isDebugEnabled()) { - StringBuffer rolesString = new StringBuffer(); - - for (int i = 0; i < roleAttributes.length; i++) { - rolesString.append(", "); - rolesString.append(roleAttributes[i]); - } - - log.debug("Searching user context '" + userContext.userPrincipal + "' for roles " - + "attributes: " + rolesString.substring(1)); - } - Attributes attrs = userContext.dirContext.getAttributes(userContext.userPrincipal, roleAttributes); - NamingEnumeration roleEnum = attrs.getAll(); - while (roleEnum.hasMore()) { - Attribute roleAttr = (Attribute)roleEnum.next(); - for (int i = 0; i < roleAttr.size(); i++) { - roles.add( roleAttr.get(i) ); - } - } - } - return roles; - } - - /** - * @return Returns the roleSuffix. - */ - public String getRoleSuffix() { - return roleSuffix; - } - - /** - * @return Returns the rootContext which to connect to; - * typically it could look something like: dc=mycompany,dc=com. - */ - public String getRootContext() { - return rootContext; - } - - /** - * @return The LDAP URL to conntect to; example: ldap://ldap.mycompany.com:389/ - */ - public String getURL() { - return url; - } - - /** Attempts to bind to the userContexts; returning on the first successful bind; - * or failing with a BadCredentialsException. - * @param username - * @param password - * @return UserContext, an innerclass holding the DirContext, and the user's LDAP Principal String. - * @throws NamingException - * @throws BadCredentialsException - */ - protected UserContext getUserContext(String username, String password) throws NamingException, BadCredentialsException { - Hashtable env = getJdniEnvironment(password); - UserContext userContext = new UserContext(); - for (int i = 0; i < userContexts.length; i++) { - env.remove(Context.SECURITY_PRINCIPAL); - userContext.userPrincipal = userContexts[i].format(new String[]{username}); - env.put(Context.SECURITY_PRINCIPAL, userContext.userPrincipal); - try { - userContext.dirContext = new InitialDirContext(env); - if (userContext.dirContext != null) { - return userContext; - } - } catch (AuthenticationException ax) { - if (log.isInfoEnabled()) { - log.info("Authentication exception for user.", ax); - } - } - } - throw new BadCredentialsException(BAD_CREDENTIALS_EXCEPTION_MESSAGE); - } - - - /** - * @return Returns the userContexts. - */ - public String[] getUserContexts() { - String[] formats = new String[userContexts.length]; - for (int i = 0; i < userContexts.length; i++) { - formats[i] = userContexts[i].toPattern(); - } - return formats; - } - - /** - * @return Returns the userRolesAttributes. - */ - public String[] getUserRolesAttributes() { - return userRolesAttributes; - } - - /** - * @FIXME When using a search (see getRolesFromContext()) I don't think this - * extra check is needed; JNDI should be responible for returning - * only the attributes requested (or maybe I don't understand JNDI - * well enough). - * - * @param Name/Id - * of the JNDI Attribute. - * - * @return Return true if the given name is a role attribute. - */ - protected boolean isRoleAttribute(String name) { - log.info("Checking rolename: " + name); - if (name != null) { - for (int i = 0; i < userRolesAttributes.length; i++) { - if (name.equals(userRolesAttributes[i])) { - return true; - } - } - } - return false; - } - - /** - * @return Returns the upperCaseRoleNames. - */ - public boolean isUpperCaseRoleNames() { - return upperCaseRoleNames; - } - - - - public UserDetails loadUserByUsernameAndPassword(String username, - String password) throws DataAccessException, - BadCredentialsException { - if ((password == null) || (password.length() == 0)) { - throw new BadCredentialsException("Empty password"); - } - - try { - if (log.isDebugEnabled()) { - log.debug("Connecting to " + getProviderURL() + " as " + username); - } - - UserContext userContext = getUserContext(username, password); - - Collection roles = getRolesFromUserContext(userContext, getUserRolesAttributes()); - Collection roles2 = getRolesFromRoleSearch(userContext, username, getRoleNameAttributes()); - if (null != roles2) { - roles.addAll(roles2); - } - - userContext.dirContext.close(); - - - if (roles.isEmpty()) { - if (null == defaultRole) { - throw new BadCredentialsException("The user has no granted " - + "authorities or the rolesAttribute is invalid"); - } else { - roles.add(defaultRole); - } - } - - String[] ldapRoles = (String[]) roles.toArray(new String[] {}); - - return new User(username, password, true, true, true, true, - getGrantedAuthorities(ldapRoles)); - } catch (AuthenticationException ex) { - throw new BadCredentialsException( - BAD_CREDENTIALS_EXCEPTION_MESSAGE, ex); - } catch (CommunicationException ex) { - throw new DataAccessResourceFailureException(ex.getRootCause() - .getMessage(), ex); - } catch (NamingException ex) { - throw new DataAccessResourceFailureException(ex.getMessage(), ex); - } - } - - /** If set to a non-null value, and a user can be bound to a LDAP Conext, - * but no role information is found then this role is automatically added. - * If null (the default) then a BadCredentialsException is thrown - * - *

For example; if you have an LDAP directory with no role information - * stored, you might simply want to give any user who can login a role of "USER".

- * - * @param defaultRole The defaultRole to set. - */ - public void setDefaultRole(String defaultRole) { - this.defaultRole = defaultRole; - } - - /** The INITIAL_CONTEXT_FACTORY used to create the JNDI Factory. - * Default is "com.sun.jndi.ldap.LdapCtxFactory"; you should not - * need to set this unless you have unusual needs. - * - * @param initialContextFactory The InitialContextFactory for creating the root JNDI context; - * defaults to "com.sun.jndi.ldap.LdapCtxFactory" - */ - public void setInitialContextFactory(String initialContextFactory) { - this.initialContextFactory = initialContextFactory; - } - - /** Name(s) of the attribute(s) for a user account object - * contaning role names assigned to the user. Leave unset if there are none. - * Consult your LDAP server administrator to determine these value(s). - * - * @param roleUserAttributes - * The roleUserAttributes to set. - */ - public void setRoleAttributesSearchFilter(String roleAttributesSearchArgs) { - this.roleAttributesSearchFilter = roleAttributesSearchArgs; - } - - /** Shortcut for setRoleContexts( new String[]{roleContext} ); */ - public void setRoleContext(String roleContext) { - setRoleContexts( new String[]{roleContext} ); - } - - /** Contexts to search for role's (which point to the user id). - *

Example, if you have a Groups object containing Groups of users then - * the expression: ou=Groups,dc=mycompany,dc=com might be used; - * alternatively, if rootContext="dc=mycompany,dc=com" then simply use "ou=Groups" here. - * - * @param roleContexts Array of MessageFormat String's for Contexts that store role information for users. - */ - public void setRoleContexts(String[] roleContexts) { - this.roleContexts = roleContexts; - } - - /** Used to build LDAP Search Filter for finding roles (in the roleContexts) - * pointing to a user. Uses MessageFormat like tokens; {0} is the - * user's DistiguishedName, {1} is the user's username. - * For more information on syntax see - * javax.naming.directory.DirContext.search(), or RFC 2254. - * - *

Example: if each group has an attribute 'memberUid' with values being - * the usernames of the user's in that group, then the value of this property - * would be (memberUid={1})

- * - * @param roleNameAttributes The roleNameAttributes to set. - */ - public void setRoleNameAttribute(String roleNameAttribute) { - setRoleNameAttributes( new String[] {roleNameAttribute} ); - } - - /** Attribute(s) of any role object returned from the roleContexts to use as role-names. - * Warning: if you do role lookups using the roleContexts and - * roleAttributesSearchFilter then you need to set roleNameAttributes or ALL attributes - * will be returned. - * - * @param roleNameAttributes The roleNameAttributes to set. - */ - public void setRoleNameAttributes(String[] roleNameAttributes) { - this.roleNameAttributes = roleNameAttributes; - } - - /** Prefix to be associated with any roles found for a user, - * defaults to an empty string. - * Older versions of this class used "ROLE_" for this value. - * - * @param rolePrefix The rolePrefix to set. - */ - public void setRolePrefix(String rolePrefix) { - this.rolePrefix = rolePrefix; - } - - /** Suffix to be associated with any roles found for a user, - * defaults to an empty string. - * - * @param roleSuffix The roleSuffix to set. - */ - public void setRoleSuffix(String roleSuffix) { - this.roleSuffix = roleSuffix; - } - - /** Root context of the LDAP Connection, if any is needed. - *

Example: dc=mycompany,dc=com

- *

Note: It is usually preferable to add this data as part of the - * userContexts and/or roleContexts attributes.

- * - * @param rootContext The rootContext which to connect to; - * typically it could look something like: dc=mycompany,dc=com. - */ - public void setRootContext(String rootContext) { - this.rootContext = rootContext; - } - - /** If true then all role name values returned from the directory - * will be converted to uppercase. - * - * @param upperCaseRoleNames The upperCaseRoleNames to set. - */ - public void setUpperCaseRoleNames(boolean upperCaseRoleNames) { - this.upperCaseRoleNames = upperCaseRoleNames; - } - - /** - * @param host The LDAP URL to conntect to; example: ldap://ldap.mycompany.com:389/ - */ - public void setURL(String url) { - this.url = url; - } - - /** Shortcut for setUserContexts( new String[]{userContext} ); */ - public void setUserContext(String userContext) { - setUserContexts( new String[]{userContext} ); - } - - /** One or more LDAP Contexts which contain user account information, use the - * MessageFormat key {0} to denote location where the user's username should - * be inserted into the expression to create a DistiguishedName. - *

Example:

cn={0},ou=Users,dc=mycompnay,dc=com

- *

Alternatively, if you had set rootContext="dc=mycompany,dc=com" then - * the first example would be rewritten as cn={0},ou=Users.

- * - * @param userContexts - * The userContexts to set. - */ - public void setUserContexts(String[] userContexts) { - this.userContexts = new MessageFormat[userContexts.length]; - for (int i = 0; i < userContexts.length; i++) { - this.userContexts[i] = new MessageFormat(userContexts[i]); - } - } - - /** Shortcut for setUserRolesAttributes(new String[]{userRolesAttribute}); */ - public void setUserRolesAttribute(String userRolesAttribute) { - this.userRolesAttributes = new String[]{userRolesAttribute}; - } - - /** Attribute(s) of any role object returned from the roleContexts to use as role-names. - * Warning: if you do role lookups using the roleContexts and - * roleAttributesSearchFilter then you need to set roleNameAttributes or ALL attributes - * will be returned. - * - * @param userRolesAttributes - * The userRolesAttributes to set. - */ - public void setUserRolesAttributes(String[] userRolesAttributes) { - this.userRolesAttributes = userRolesAttributes; - } +import net.sf.acegisecurity.BadCredentialsException; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.UserDetails; +import net.sf.acegisecurity.providers.dao.PasswordAuthenticationDao; +import net.sf.acegisecurity.providers.dao.User; +/** + * A much simplified (vs the 1.6 revision) LdapPasswordAuthenticationDao, + * which should meet all 'basic' needs, leaving advanced options such as + * multiple user and/or role contexts. This version assumes all users + * are in one context, and roles are assigned via attributes of the user's + * directory object. Authentication is done by creating a username for + *

+ * + *

Examples:

+ *

The following examples would be linked into the main Acegi + * configuration by:
+ *
+ *
+ *
+ *

+ *

+ *

+ * + *
'Standard' LDAP Settings
+ *

+ *
+ * ldap://localhost:389/ou=system
+ * uid={0},ou=users,ou=system
+ * uid={0},ou=users
+ *

+ *

+ * + *
Active Directory Configuration
+ *

+ * I haven't been able to test this directly, + * but something like the following should do the trick:
+ *
+ * ldap://localhost:389/ou=system
+ * {0}@adDomainName
+ *

+ * (if anyone gets this to work please let me know so I can include it + * in the documentation). + *

+ * + * + * @author Karel Miarka + * @author Daniel Miller + * @author Robert Sanders + */ +public class LdapPasswordAuthenticationDao extends InitialDirContextFactoryBean implements PasswordAuthenticationDao { + + private static final Log logger = LogFactory.getLog(LdapPasswordAuthenticationDao.class); + + public static final String BAD_CREDENTIALS_EXCEPTION_MESSAGE = "Invalid username, password or server configuration (JNDI Context)."; + + /** Pattern used to turn the user's supplied username into the + * username format the LDAP server expects. {0} is the username. + * + *

+ * Examples:
+ * 'Normal' LDAP: "cn={0}"
+ * Active Directory style LDAP: "{0}@AD_Domain" + *

+ */ + private MessageFormat usernameFormat = new MessageFormat("cn={0}"); + + /** Message format used to create the Name within the LDAP Context + * from which role information will be retrieved. + * {0} is the username + * {1} is the result of usernameFormat.format(new Object[] {username}) + * + *

Example: "uid={0},ou=users"

+ * + */ + private MessageFormat userLookupNameFormat = null; + + /** List of LDAP Attributes which contian role name information. */ + private String[] roleAttributes = {"memberOf"}; + + /** The role, which if non-null, will be grated the user if the user has no other roles. */ + private String defaultRole = null; + + public UserDetails loadUserByUsernameAndPassword(String username, String password) throws DataAccessException, BadCredentialsException { + if ((password == null) || (password.length() == 0)) { + throw new BadCredentialsException("Empty password"); + } + + String ldapUsername = (null == usernameFormat) ? username : usernameFormat.format( new Object[]{username} ); + if (logger.isDebugEnabled()) { + logger.debug("Connecting to " + this.getUrl() + " as " + ldapUsername); + } + + InitialDirContext dirContext = null; + try { + dirContext = newInitialDirContext(ldapUsername, password); + } catch (AuthenticationException ax) { + throw new BadCredentialsException(BAD_CREDENTIALS_EXCEPTION_MESSAGE, ax); + } + if (null == dirContext) { + throw new BadCredentialsException(BAD_CREDENTIALS_EXCEPTION_MESSAGE); + } + + String[] roles = null; + if (null != roleAttributes) { + try { + String userContextName = (null == userLookupNameFormat) ? "" : + userLookupNameFormat.format(new Object[]{username, ldapUsername}); + roles = getRolesFromContext(dirContext, userContextName); + } catch (NamingException e) { + throw new DataAccessResourceFailureException("Unable to retrieve role information from LDAP Server.", e); + } + } + if ((null == roles) && (null != defaultRole)) { + roles = new String[] {defaultRole}; + } + + return new User(username, password, true, true, true, true, toGrantedAuthority(roles) ); + } + + /** Converts from an Array of String rolenames to an array of GrantedAuthority objects. + * This is a possible extension point for sub-classes to enrich the behavior of how + * the GrantedAuthority array is constucted. + * + * @param rolenames Array of Strings representing the names of the + * roles that the user has been granted according to the LDAP server. + * @return Array of GrantedAuthority objects which will be associated with the User's UserDetails. + */ + protected GrantedAuthority[] toGrantedAuthority(String[] rolenames) { + if ((null == rolenames) || (rolenames.length == 0)) { + return null; + } + + GrantedAuthority[] grantedAuthorities = new GrantedAuthority[rolenames.length]; + for (int i = 0; i < rolenames.length; i++) { + grantedAuthorities[i] = toGrantedAuthority(rolenames[i]); + } + + return grantedAuthorities; + } + + /** This is a possible extension point for sub-classes to enrich the behavior of how + * the GrantedAuthority object is constucted. + * + * @param rolename Name of an LDAP role which is granted to the user. + * @return GrantedAuthority object which represents the granted role. + */ + protected GrantedAuthority toGrantedAuthority(String rolename) { + GrantedAuthority ga = new GrantedAuthorityImpl( rolename ); + return ga; + } + + /** + * + * @param ctx The LDAP DirContext retrieved by the user login. + * @return An array of roles granted the user found by searching all attributes named in the roleAttributes field. + * @throws NamingException + */ + protected String[] getRolesFromContext(DirContext ctx, String dnOfUser) throws NamingException { + if (null == roleAttributes) { + return null; + } + + if (logger.isDebugEnabled()) { + String rolesString = ""; + + for (int i = 0; i < roleAttributes.length; i++) { + rolesString += (", " + roleAttributes[i]); + } + + logger.debug("Searching ldap context for roles using attributes: " + rolesString.substring(1)); + } + + ArrayList roles = new ArrayList(); + Attributes attrs = null; + if (null == usernameFormat) { + attrs = ctx.getAttributes("", roleAttributes); + } else { + attrs = ctx.getAttributes(dnOfUser, roleAttributes); + } + + if (null != attrs) { + NamingEnumeration attrsEnum = attrs.getAll(); + while ((attrsEnum != null) && (attrsEnum.hasMore())) { + Attribute attr = (Attribute)attrsEnum.next(); + if (null != attr) { + ArrayList list = getRolesFromAttribute( attr ); + if (null != list) { + roles.addAll( list ); + } + } + } + } + + if (roles.isEmpty()) { + return null; + } else { + return (String[])roles.toArray( new String[roles.size()] ); + } + } + + protected ArrayList getRolesFromAttribute(Attribute attr) throws NamingException { + NamingEnumeration rolesEnum = attr.getAll(); + if (null == rolesEnum) { + return null; + } + + ArrayList roles = new ArrayList(); + while (rolesEnum.hasMore()) { + String rolename = (String)rolesEnum.next(); + if (null != rolename) { + roles.add( convertLdapRolename(rolename) ); + } + } + return roles; + } + + protected String convertLdapRolename(String name) { + //System.out.println("Found LDAP UserRole = " + name); + return name.toUpperCase(); + } + + public String getDefaultRole() { + return defaultRole; + } + + public void setDefaultRole(String defaultRole) { + this.defaultRole = defaultRole; + } + + public String[] getRoleAttributes() { + return roleAttributes; + } + + /** List of LDAP Attributes which contian role name information. */ + public void setRoleAttributes(String[] roleAttributes) { + this.roleAttributes = roleAttributes; + } + + /** Utility method to set a single attribute which contains role information. + * @see setRoleAttributes + */ + public void setRoleAttribute(String roleAttribute) { + setRoleAttributes(new String[]{ roleAttribute }); + } + + public String getUsernameFormat() { + if (null == usernameFormat) { + return null; + } else { + return usernameFormat.toPattern(); + } + } + + /** Pattern used to turn the user's supplied username into the + * username format the LDAP server expects. + * + *

+ * Examples:
+ * 'Normal' LDAP: "cn={0}"
+ * Active Directory style LDAP: "{0}@AD_Domain" + *

+ */ + public void setUsernameFormat(String usernameFormat) { + if (null == usernameFormat) { + this.usernameFormat = null; + } else { + this.usernameFormat = new MessageFormat(usernameFormat); + } + } + + + public String getUserLookupNameFormat() { + if (null == userLookupNameFormat) { + return null; + } else { + return userLookupNameFormat.toPattern(); + } + } + + public void setUserLookupNameFormat(String userLookupNameFormat) { + if (null == userLookupNameFormat) { + this.userLookupNameFormat = null; + } else { + this.userLookupNameFormat = new MessageFormat(userLookupNameFormat); + } + } }