From 7beab1238bb7c286f995c27d1b71868377de97ba Mon Sep 17 00:00:00 2001
From: Robert Sanders Example use:
+* 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:
+* PasswordAuthenticationDao implementation
- * using LDAP service for user authentication.
- *
-*
-* <bean id="ldapDaoImpl" class="net.sf.acegisecurity.providers.dao.ldap.LdapPasswordAuthenticationDao">
-* <property name="host"><value>sydney.ipov.info</value></property>
-* <property name="rootContext"><value>dc=ipov,dc=info</value></property>
-* <property name="userContext"><alue>ou=Users</value></property>
-* <property name="userAttribute"><value>uid</value></property>
-* </bean>
-* ...
-* <bean id="authenticationProvider" class="net.sf.acegisecurity.providers.dao.PasswordDaoAuthenticationProvider">
-* <property name="passwordAuthenticationDao"><ref local="ldapDaoImpl"/></property>
-* </bean>
+*
+*
+*
+*
+* <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 { - //~ Static fields/initializers ============================================= - - public static final String BAD_CREDENTIALS_EXCEPTION_MESSAGE = "Invalid username, password or context"; - private static final transient Log log = LogFactory.getLog(LdapPasswordAuthenticationDao.class); - - //~ Instance fields ======================================================== - - private String host; - - /** The INITIAL_CONTEXT_FACTORY for use with JNDI. */ - private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory"; - private String rootContext; - private String userAttribute = "CN"; // ??? is this the right code?? - private String userContext = "CN=Users"; - private String[] rolesAttributes = {"memberOf"}; - private int port = 389; - - //~ Methods ================================================================ - - /** - * Set hostname or IP address of the host running LDAP server. - * - * @param hostname DOCUMENT ME! - */ - public void setHost(String hostname) { - this.host = hostname; - } - - /** - * DOCUMENT ME! - * - * @return Returns the host. - */ - public String getHost() { - return host; - } - - /** - * DOCUMENT ME! - * - * @param initialContextFactory The initialContextFactory to set. - */ - public void setInitialContextFactory(String initialContextFactory) { - this.initialContextFactory = initialContextFactory; - } - - /** - * DOCUMENT ME! - * - * @return Returns the initialContextFactory. - */ - public String getInitialContextFactory() { - return initialContextFactory; - } - - /** - * Set the port on which is running the LDAP server.
GrantedAuthority is created. Default
- * value: { "memberOf" }.
- *
- * @param rolesAttributes DOCUMENT ME!
- */
- public void setRolesAttributes(String[] rolesAttributes) {
- this.rolesAttributes = rolesAttributes;
- }
-
- /**
- * Set the root context to which you attempt to log in. 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) {
- GrantedAuthority ga = new GrantedAuthorityImpl("ROLE_"
- + ldapRole.toUpperCase());
-
- if (log.isDebugEnabled()) {
- log.debug("GrantedAuthority: " + ga);
- }
-
- return ga;
- }
-
- /**
- * DOCUMENT ME!
- *
- * @param name DOCUMENT ME!
- *
- * @return Return true if the given name is a role attribute.
- */
- protected boolean isRoleAttribute(String name) {
- if (name != null) {
- for (int i = 0; i < rolesAttributes.length; i++) {
- if (name.equals(rolesAttributes[i])) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Get the attributes to that contain role information. This function may
- * be overridden in a subclass.
- *
- * @return DOCUMENT ME!
- */
- protected String[] getRolesAttributeNames() {
- return rolesAttributes;
- }
-
- protected Collection getRolesFromContext(DirContext ctx,
- String userContext, String username, String[] roleAttributes)
- throws NamingException {
- List roles = new ArrayList();
-
- if (log.isDebugEnabled()) {
- String rolesString = "";
-
- for (int i = 0; i < roleAttributes.length; i++) {
- rolesString += (", " + roleAttributes[i]);
- }
-
- log.debug("Searching user context '" + userContext + "' for roles "
- + "attributes: " + rolesString.substring(1));
- }
-
- NamingEnumeration answer = ctx.search(userContext,
- getUsernameAttributes(username), roleAttributes);
-
- while (answer.hasMore()) {
- SearchResult sr = (SearchResult) answer.next();
- NamingEnumeration attrs = sr.getAttributes().getAll();
-
- while (attrs.hasMore()) {
- Attribute attr = (Attribute) attrs.next();
-
- if (isRoleAttribute(attr.getID())) {
- NamingEnumeration rolesAttr = attr.getAll();
-
- while (rolesAttr.hasMore()) {
- String role = (String) rolesAttr.next();
- roles.add(role);
-
- if (log.isDebugEnabled()) {
- log.debug("Role read: " + attr.getID() + "=" + role);
- }
- }
- }
- }
- }
-
- return roles;
- }
-
- /**
- * Get the Context.SECURITY_PRINCIPAL for the given username
- * string. This implementation returns the userBase for JNDI / LDAP
- * lookup.
- *
- * @param username DOCUMENT ME!
- *
- * @return DOCUMENT ME!
- */
- protected String getUserPrincipal(String username) {
- StringBuffer principal = new StringBuffer(userAttribute);
- principal.append("=");
- principal.append(username);
- principal.append(",");
- principal.append(this.userContext);
- principal.append(",");
- principal.append(this.rootContext);
-
- return principal.toString();
- }
-
- /**
- * 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!
- */
- protected Attributes getUsernameAttributes(String username) {
- Attributes matchAttrs = new BasicAttributes(true); // ignore case
- matchAttrs.put(new BasicAttribute("distinguishedName",
- getUserPrincipal(username)));
-
- return matchAttrs;
- }
+
+ /** 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);
+
+ if (log.isDebugEnabled()) {
+ log.debug("GrantedAuthority: " + ga);
+ }
+
+ return ga;
+ }
+
+ /**
+ * @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,
+ 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; + } + }