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 arrayGrantedAuthorities 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 + *The following examples would be linked into the main Acegi
+ * configuration by:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * I haven't been able to test this directly,
+ * but something like the following should do the trick:
+ *
+ *
+ *
+ *
+ * (if anyone gets this to work please let me know so I can include it
+ * in the documentation).
+ *
+ * Examples:
+ * 'Normal' LDAP: "cn={0}"
+ * Active Directory style LDAP: "{0}@AD_Domain"
+ *
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"
+ *