Index: security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/spi/LdapExtAdLoginModule.java =================================================================== --- security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/spi/LdapExtAdLoginModule.java (working copy) +++ security-jboss-sx/jbosssx/src/main/java/org/jboss/security/auth/spi/LdapExtAdLoginModule.java (working copy) @@ -23,9 +23,11 @@ import java.security.Principal; import java.security.acl.Group; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.StringTokenizer; import java.util.Map.Entry; @@ -49,11 +51,12 @@ import org.jboss.security.vault.SecurityVaultUtil; /** - The org.jboss.security.auth.spi.LdapExtLoginModule, added in jboss-4.0.3, is an + The org.jboss.security.auth.spi.LdapExtAdLoginModule, is an alternate ldap login module implementation that uses searches for locating both the user to bind as for authentication as well as the associated roles. The roles query will recursively follow distinguished names (DNs) to navigate a - hierarchical role structure. + hierarchical role structure. This module is based on the org.jboss.security.auth.spi.LdapExtLoginModule, + but it is better optimized of ActiveDirectory based authentication. The LoginModule options include whatever options your LDAP JNDI provider supports. Examples of standard property names are: @@ -66,7 +69,7 @@ The authentication happens in 2 steps: # An initial bind to the ldap server is done using the __bindDN__ and __bindCredential__ options. The __bindDN__ is some user with the ability to - search both the __baseDN__ and __rolesCtxDN__ trees for the user and roles. The + search the __baseDN__ tree and the tree containing the user roles. The user DN to authenticate against is queried using the filter specified by the __baseFilter__ attribute (see the __baseFilter__ option description for its syntax). @@ -77,8 +80,7 @@ obtained by the callback handler. If this is successful, the associated user roles are queried using the - __rolesCtxDN__, __roleAttributeID__, __roleAttributeIsDN__, - __roleNameAttributeID__, and __roleFilter__ options. + __roleAttributeID__, __roleNameAttributeID__, and __parseRoleNameFromDN__ options. The full odule properties include: * __baseCtxDN__ : The fixed DN of the context to start the user search from. @@ -96,35 +98,18 @@ authenticate. The input username/userDN as obtained from the login module callback will be substituted into the filter anywhere a "{0}" expression is seen. This substituion behavior comes from the standard - __DirContext.search(Name, String, Object[], SearchControls cons)__ method. An - common example search filter is "(uid={0})". - * __rolesCtxDN__ : The fixed DN of the context to search for user roles. - Consider that this is not the Distinguished Name of where the actual roles are; - rather, this is the DN of where the objects containing the user roles are (e.g. - for active directory, this is the DN where the user account is) - * __roleFilter__ : A search filter used to locate the roles associated with the - authenticated user. The input username/userDN as obtained from the login module - callback will be substituted into the filter anywhere a "{0}" expression is - seen. The authenticated userDN will be substituted into the filter anywhere a - "{1}" is seen. An example search filter that matches on the input username is: - "(member={0})". An alternative that matches on the authenticated userDN is: - "(member={1})". - * __roleAttributeIsDN__ : A flag indicating whether the user's role attribute - contains the fully distinguished name of a role object, or the users's role - attribute contains the role name. If false, the role name is taken from the - value of the user's role attribute. If true, the role attribute represents the - distinguished name of a role object. The role name is taken from the value of - the roleNameAttributeId` attribute of the corresponding object. In certain - directory schemas (e.g., Microsoft Active Directory), role (group)attributes in - the user object are stored as DNs to role objects instead of as simple names, in - which case, this property should be set to true. The default value of this - property is false. + __DirContext.search(Name, String, Object[], SearchControls cons)__ method. A + common example search filter for Active Directory is "(sAMAccountName={0})". + * __roleAttributeID__ : The name of the attribute of the user or role object which + corresponds to the role the user/role is a member of. This attribute is assumed the + contain the DN of the referenced role. * __roleNameAttributeID__ : The name of the attribute of the role object which - corresponds to the name of the role. If the __roleAttributeIsDN__ property is - set to true, this property is used to find the role object's name attribute. If - the __roleAttributeIsDN__ property is set to false, this property is ignored. + corresponds to the name of the role. If the __parseRoleNameFromDN__ property is set, + then this attribute is not queried from LDAP, rather it is used to parse the role name + from the role DN. * __roleRecursion__ : How deep the role search will go below a given matching - context. Disable with 0, which is the default. + context. Disable depth checking with -1, which is the default, set to 0 to only + query the roles that are directly associated with the user. * __searchTimeLimit__ : The timeout in milliseconds for the user/role searches. Defaults to 10000 (10 seconds). * __searchScope__ : Sets the search scope to one of the strings. The default is @@ -142,52 +127,40 @@ @author Andy Oliver @author Scott.Stark@jboss.org + @author Radics Péter @version $Revision$ */ -@SuppressWarnings("rawtypes") -public class LdapExtLoginModule extends UsernamePasswordLoginModule +public class LdapExtAdLoginModule extends UsernamePasswordLoginModule { // see AbstractServerLoginModule - private static final String ROLES_CTX_DN_OPT = "rolesCtxDN"; private static final String ROLE_ATTRIBUTE_ID_OPT = "roleAttributeID"; - private static final String ROLE_ATTRIBUTE_IS_DN_OPT = "roleAttributeIsDN"; private static final String ROLE_NAME_ATTRIBUTE_ID_OPT = "roleNameAttributeID"; private static final String PARSE_ROLE_NAME_FROM_DN_OPT = "parseRoleNameFromDN"; private static final String BIND_DN = "bindDN"; private static final String BIND_CREDENTIAL = "bindCredential"; private static final String BASE_CTX_DN = "baseCtxDN"; private static final String BASE_FILTER_OPT = "baseFilter"; - private static final String ROLE_FILTER_OPT = "roleFilter"; private static final String ROLE_RECURSION = "roleRecursion"; private static final String DEFAULT_ROLE = "defaultRole"; private static final String SEARCH_TIME_LIMIT_OPT = "searchTimeLimit"; private static final String SEARCH_SCOPE_OPT = "searchScope"; private static final String SECURITY_DOMAIN_OPT = "jaasSecurityDomain"; private static final String DISTINGUISHED_NAME_ATTRIBUTE_OPT = "distinguishedNameAttribute"; - private static final String PARSE_USERNAME = "parseUsername"; - private static final String USERNAME_BEGIN_STRING = "usernameBeginString"; - private static final String USERNAME_END_STRING = "usernameEndString"; private static final String ALLOW_EMPTY_PASSWORDS = "allowEmptyPasswords"; private static final String[] ALL_VALID_OPTIONS = { - ROLES_CTX_DN_OPT, ROLE_ATTRIBUTE_ID_OPT, - ROLE_ATTRIBUTE_IS_DN_OPT, ROLE_NAME_ATTRIBUTE_ID_OPT, PARSE_ROLE_NAME_FROM_DN_OPT, BIND_DN, BIND_CREDENTIAL, BASE_CTX_DN, BASE_FILTER_OPT, - ROLE_FILTER_OPT, ROLE_RECURSION, DEFAULT_ROLE, SEARCH_TIME_LIMIT_OPT, SEARCH_SCOPE_OPT, SECURITY_DOMAIN_OPT, DISTINGUISHED_NAME_ATTRIBUTE_OPT, - PARSE_USERNAME, - USERNAME_BEGIN_STRING, - USERNAME_END_STRING, ALLOW_EMPTY_PASSWORDS, Context.INITIAL_CONTEXT_FACTORY, @@ -215,17 +188,13 @@ protected String baseFilter; - protected String rolesCtxDN; - - protected String roleFilter; - protected String roleAttributeID; protected String roleNameAttributeID; - protected boolean roleAttributeIsDN; + protected boolean parseRoleNameFromDN; - protected boolean parseRoleNameFromDN; + protected String[] attributeIDs; protected int recursion = 0; @@ -235,23 +204,17 @@ protected String distinguishedNameAttribute; - protected boolean parseUsername; - - protected String usernameBeginString; - - protected String usernameEndString; - // simple flag to indicate is the validatePassword method was called protected boolean isPasswordValidated = false; - public LdapExtLoginModule() + public LdapExtAdLoginModule() { } private transient SimpleGroup userRoles = new SimpleGroup("Roles"); - @SuppressWarnings("unchecked") - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { addValidOptions(ALL_VALID_OPTIONS); super.initialize(subject, callbackHandler, sharedState, options); @@ -262,6 +225,7 @@ user's password. We also override the validatePassword so this is ok. @return and empty password String */ + @Override protected String getUsersPassword() throws LoginException { return ""; @@ -275,6 +239,7 @@ rather than the security domain identity. @return Group[] containing the sets of roles */ + @Override protected Group[] getRoleSets() throws LoginException { // SECURITY-225: check if authentication was already done in a previous login module @@ -306,6 +271,7 @@ @param inputPassword the password to validate. @param expectedPassword ignored */ + @Override protected boolean validatePassword(String inputPassword, String expectedPassword) { isPasswordValidated = true; @@ -348,8 +314,8 @@ */ private void defaultRole() { - String defaultRole = (String) options.get(DEFAULT_ROLE); - try + String defaultRole = (String) options.get(DEFAULT_ROLE); + try { if (defaultRole == null || defaultRole.equals("")) { @@ -394,22 +360,23 @@ baseDN = (String) options.get(BASE_CTX_DN); baseFilter = (String) options.get(BASE_FILTER_OPT); - roleFilter = (String) options.get(ROLE_FILTER_OPT); + if (baseFilter == null) + baseFilter = "(sAMAccountName={0})"; roleAttributeID = (String) options.get(ROLE_ATTRIBUTE_ID_OPT); if (roleAttributeID == null) - roleAttributeID = "role"; - // Is user's role attribute a DN or the role name - String roleAttributeIsDNOption = (String) options.get(ROLE_ATTRIBUTE_IS_DN_OPT); - roleAttributeIsDN = Boolean.valueOf(roleAttributeIsDNOption).booleanValue(); + roleAttributeID = "memberOf"; roleNameAttributeID = (String) options.get(ROLE_NAME_ATTRIBUTE_ID_OPT); if (roleNameAttributeID == null) - roleNameAttributeID = "name"; + roleNameAttributeID = "cn"; //JBAS-4619:Parse Role Name from DN String parseRoleNameFromDNOption = (String) options.get(PARSE_ROLE_NAME_FROM_DN_OPT); parseRoleNameFromDN = Boolean.valueOf(parseRoleNameFromDNOption).booleanValue(); + if (parseRoleNameFromDN) + attributeIDs = new String[] { roleAttributeID }; + else + attributeIDs = new String[] { roleNameAttributeID, roleAttributeID }; - rolesCtxDN = (String) options.get(ROLES_CTX_DN_OPT); String strRecursion = (String) options.get(ROLE_RECURSION); try { @@ -417,9 +384,9 @@ } catch (NumberFormatException e) { - PicketBoxLogger.LOGGER.debugFailureToParseNumberProperty(ROLE_RECURSION, 0); - // its okay for this to be 0 as this just disables recursion - recursion = 0; + PicketBoxLogger.LOGGER.debugFailureToParseNumberProperty(ROLE_RECURSION, -1); + // its okay for this to be -1 as this just disables recursion depth checking + recursion = -1; } String timeLimit = (String) options.get(SEARCH_TIME_LIMIT_OPT); if (timeLimit != null) @@ -457,11 +424,7 @@ String userDN = bindDNAuthentication(ctx, username, credential, baseDN, baseFilter); // Query for roles matching the role filter - SearchControls constraints = new SearchControls(); - constraints.setSearchScope(searchScope); - constraints.setReturningAttributes(new String[0]); - constraints.setTimeLimit(searchTimeLimit); - rolesSearch(ctx, constraints, username, userDN, recursion, 0); + rolesSearch(ctx, userDN); } catch(Exception e) { @@ -495,7 +458,7 @@ String attrList[] = {distinguishedNameAttribute}; constraints.setReturningAttributes(attrList); - NamingEnumeration results = null; + NamingEnumeration results = null; Object[] filterArgs = {user}; results = ctx.search(baseDN, filter, filterArgs, constraints); @@ -537,115 +500,71 @@ return userDN; } - + /** @param ctx - @param constraints - @param user @param userDN - @param recursionMax - @param nesting - @throws NamingException + @throws NamingException */ - protected void rolesSearch(InitialLdapContext ctx, SearchControls constraints, String user, String userDN, - int recursionMax, int nesting) throws NamingException + protected void rolesSearch(InitialLdapContext ctx, String userDN) throws NamingException { - Object[] filterArgs = {user, userDN}; - NamingEnumeration results = ctx.search(rolesCtxDN, roleFilter, filterArgs, constraints); - try - { - while (results.hasMore()) - { - SearchResult sr = (SearchResult) results.next(); - String dn = canonicalize(sr.getName()); - if (nesting == 0 && roleAttributeIsDN && roleNameAttributeID != null) - { - if(parseRoleNameFromDN) + Set processedDNs = new HashSet(); + Attributes attributes = ctx.getAttributes(userDN, new String[]{ roleAttributeID } ); + processRoleAttribute(ctx, attributes.get(roleAttributeID), processedDNs, 0); + } + + private void processRoleAttribute(InitialLdapContext ctx, Attribute roleAttribute, Set processedDNs, int depth) throws NamingException + { + if (roleAttribute == null) + return; + int numRoleAttributes = roleAttribute.size(); + for (int i = 0; i < numRoleAttributes; ++i) + { + String roleDN = (String)roleAttribute.get(i); + rolesSearch(ctx, roleDN, processedDNs, depth); + } + } + + private void rolesSearch(InitialLdapContext ctx, String roleDN, Set processedDNs, int depth) { + if (!processedDNs.add(roleDN)) + return; + try + { + Attributes attributes = ctx.getAttributes(roleDN, attributeIDs); + if (parseRoleNameFromDN) + parseRole(roleDN); + else + { + Attribute roleNameAttribute = attributes.get(roleNameAttributeID); + if (roleNameAttribute != null) { - parseRole(dn); + int numRoleNames = roleNameAttribute.size(); + for (int i = 0; i < numRoleNames; ++i) + { + String roleName = (String)roleNameAttribute.get(i); + addRole(roleName); + } } - else - { - // Check the top context for role names - String[] attrNames = {roleNameAttributeID}; - Attributes result2 = ctx.getAttributes(dn, attrNames); - Attribute roles2 = result2.get(roleNameAttributeID); - if( roles2 != null ) - { - for(int m = 0; m < roles2.size(); m ++) - { - String roleName = (String) roles2.get(m); - addRole(roleName); - } - } - } - } - - // Query the context for the roleDN values - String[] attrNames = {roleAttributeID}; - Attributes result = ctx.getAttributes(dn, attrNames); - if (result != null && result.size() > 0) - { - Attribute roles = result.get(roleAttributeID); - for (int n = 0; n < roles.size(); n++) - { - String roleName = (String) roles.get(n); - if(roleAttributeIsDN && parseRoleNameFromDN) - { - parseRole(roleName); - } - else if (roleAttributeIsDN) - { - // Query the roleDN location for the value of roleNameAttributeID - String roleDN = roleName; - String[] returnAttribute = {roleNameAttributeID}; - try - { - Attributes result2 = ctx.getAttributes(roleDN, returnAttribute); - Attribute roles2 = result2.get(roleNameAttributeID); - if (roles2 != null) - { - for (int m = 0; m < roles2.size(); m++) - { - roleName = (String) roles2.get(m); - addRole(roleName); - } - } - } - catch (NamingException e) - { - PicketBoxLogger.LOGGER.debugFailureToQueryLDAPAttribute(roleNameAttributeID, roleDN, e); - } - } - else - { - // The role attribute value is the role name - addRole(roleName); - } - } - } - - if (nesting < recursionMax) - { - rolesSearch(ctx, constraints, user, dn, recursionMax, nesting + 1); - } - } - } - finally - { - if (results != null) - results.close(); - } - + } + if (recursion < 0 || depth < recursion) + { + processRoleAttribute(ctx, attributes.get(roleAttributeID), processedDNs, depth + 1); + } + } + catch (Throwable e) + { + PicketBoxLogger.LOGGER.debugFailureToQueryLDAPAttribute(roleNameAttributeID, roleDN, e); + } + } private InitialLdapContext constructInitialLdapContext(String dn, Object credential) throws NamingException { Properties env = new Properties(); - Iterator iter = options.entrySet().iterator(); + Iterator iter = options.entrySet().iterator(); while (iter.hasNext()) { - Entry entry = (Entry) iter.next(); + Entry entry = (Entry) iter.next(); env.put(entry.getKey(), entry.getValue()); } @@ -674,24 +593,6 @@ return new InitialLdapContext(env, null); } - //JBAS-3438 : Handle "/" correctly - private String canonicalize(String searchResult) - { - String result = searchResult; - int len = searchResult.length(); - - String appendRolesCtxDN = "" + ("".equals(rolesCtxDN) ? "" : "," + rolesCtxDN); - if (searchResult.endsWith("\"")) - { - result = searchResult.substring(0, len - 1) + appendRolesCtxDN + "\""; - } - else - { - result = searchResult + appendRolesCtxDN; - } - return result; - } - private void addRole(String roleName) { if (roleName != null) @@ -715,38 +616,13 @@ while(st != null && st.hasMoreTokens()) { String keyVal = st.nextToken(); - if(keyVal.indexOf(roleNameAttributeID) > -1) + StringTokenizer kst = new StringTokenizer(keyVal, "="); + String key = kst.nextToken(); + if (roleNameAttributeID.equalsIgnoreCase(key)) { - StringTokenizer kst = new StringTokenizer(keyVal,"="); - kst.nextToken(); - addRole(kst.nextToken()); + addRole(kst.nextToken()); } } } - - protected String getUsername() - { - String username = super.getUsername(); - parseUsername = Boolean.valueOf((String) options.get(PARSE_USERNAME)); - if (parseUsername) - { - usernameBeginString = (String) options.get(USERNAME_BEGIN_STRING); - usernameEndString = (String) options.get(USERNAME_END_STRING); - int beginIndex = 0; - if (usernameBeginString != null && !usernameBeginString.equals("")) - beginIndex = username.indexOf(usernameBeginString) + usernameBeginString.length(); - if (beginIndex == -1) // not allowed. reset - beginIndex = 0; - int endIndex = username.length(); - if (usernameEndString != null && !usernameEndString.equals("")) - endIndex = username.substring(beginIndex).indexOf(usernameEndString); - if (endIndex == -1) // not allowed. reset - endIndex = username.length(); - else - endIndex += beginIndex; - username = username.substring(beginIndex, endIndex); - } - return username; - } }