Index: java/org/apache/catalina/realm/JNDIRealm.java =================================================================== --- java/org/apache/catalina/realm/JNDIRealm.java (revision 758654) +++ java/org/apache/catalina/realm/JNDIRealm.java (working copy) @@ -317,15 +317,7 @@ */ protected MessageFormat[] userPatternFormatArray = null; - /** - * The maximum recursion depth when resolving roles recursively. - * By default we don't resolve roles recursively. - */ - protected int roleRecursionLimit = 0; - - - /** * The base element for role searches. */ protected String roleBase = ""; @@ -362,7 +354,13 @@ * Should we search the entire subtree for matching memberships? */ protected boolean roleSubtree = false; + + /** + * Should we look for nested group in order to determine roles? + */ + protected boolean roleNested = false; + /** * An alternate URL, to which, we should connect if connectionURL fails. */ @@ -652,28 +650,6 @@ /** - * Return the maximum recursion depth for role searches. - */ - public int getRoleRecursionLimit() { - - return (this.roleRecursionLimit); - - } - - - /** - * Set the maximum recursion depth for role searches. - * - * @param roleRecursionLimit The new recursion limit - */ - public void setRoleRecursionLimit(int roleRecursionLimit) { - - this.roleRecursionLimit = roleRecursionLimit; - - } - - - /** * Return the base element for role searches. */ public String getRoleBase() { @@ -763,9 +739,31 @@ this.roleSubtree = roleSubtree; } + + /** + * Return the "The nested group search flag" flag. + */ + public boolean getRoleNested() { + return (this.roleNested); + } + + /** + * Set the "search subtree for roles" flag. + * + * @param roleNested The nested group search flag + */ + public void setRoleNested(boolean roleNested) { + + this.roleNested = roleNested; + + } + + + + /** * Return the password attribute used to retrieve the user password. */ public String getUserPassword() { @@ -1546,72 +1544,7 @@ return (validated); } - /** - * Add roles to a user and search for other roles containing them themselves. - * We search recursively with a limited depth. - * By default the depth is 0, and we only use direct roles. - * The search needs to use the distinguished role names, - * but to return the role names. - * - * @param depth Recursion depth, starting at zero - * @param context The directory context we are searching - * @param recursiveMap The cumulative result map of role names and DNs. - * @param recursiveSet The cumulative result set of role names. - * @param groupName The role name to add to the list. - * @param groupDName The distinguished name of the role. - * - * @exception NamingException if a directory server error occurs - */ - private void getRolesRecursive(int depth, DirContext context, Map recursiveMap, Set recursiveSet, - String groupName, String groupDName) throws NamingException { - if (containerLog.isTraceEnabled()) - containerLog.trace("Recursive search depth " + depth + " for group '" + groupDName + " (" + groupName + ")'"); - // Adding the given group to the result set if not already found - if (!recursiveSet.contains(groupDName)) { - recursiveSet.add(groupDName); - recursiveMap.put(groupDName, groupName); - if (depth >= roleRecursionLimit) { - if (roleRecursionLimit > 0) - containerLog.warn("Terminating recursive role search because of recursion limit " + - roleRecursionLimit + ", results might be incomplete"); - return; - } - // Prepare the parameters for searching groups - String filter = roleFormat.format(new String[] { groupDName }); - SearchControls controls = new SearchControls(); - controls.setSearchScope(roleSubtree ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE); - controls.setReturningAttributes(new String[] { roleName }); - if (containerLog.isTraceEnabled()) { - containerLog.trace("Recursive search in role base '" + roleBase + "' for attribute '" + roleName + "'" + - " with filter expression '" + filter + "'"); - } - // Searching groups that assign the given group - NamingEnumeration results = - context.search(roleBase, filter, controls); - if (results != null) { - // Iterate over the resulting groups - try { - while (results.hasMore()) { - SearchResult result = results.next(); - Attributes attrs = result.getAttributes(); - if (attrs == null) - continue; - String dname = getDistinguishedName(context, roleBase, result); - String name = getAttributeValue(roleName, attrs); - if (name != null && dname != null) { - getRolesRecursive(depth+1, context, recursiveMap, recursiveSet, name, dname); - } - } - } catch (PartialResultException ex) { - if (!adCompat) - throw ex; - } - } - } - } - - /** * Return a List of roles associated with the given User. Any * roles present in the user's directory entry are supplemented by * a directory search. If no roles are associated with this user, @@ -1654,7 +1587,7 @@ // Are we configured to do role searches? if ((roleFormat == null) || (roleName == null)) return (list); - + // Set up parameters for an appropriate search String filter = roleFormat.format(new String[] { doRFC2254Encoding(dn), username }); SearchControls controls = new SearchControls(); @@ -1691,30 +1624,60 @@ Set keys = groupMap.keySet(); if (containerLog.isTraceEnabled()) { containerLog.trace(" Found " + keys.size() + " direct roles"); - for (Iterator i = keys.iterator(); i.hasNext();) { - Object k = i.next(); - containerLog.trace( " Found direct role " + k + " -> " + groupMap.get(k)); + for (String key: keys) { + containerLog.trace( " Found direct role " + key + " -> " + groupMap.get(key)); } } + + // if nested group search is enabled, perform searches for nested groups until no new group is found + if (getRoleNested()) { + + // The following efficient algorithm is known as memberOf Algorithm, as described in "Practices in + // Directory Groups". It avoids group slurping and handles cyclic group memberships as well. + // See http://middleware.internet2.edu/dir/ for details + + Set newGroupDNs = new HashSet(groupMap.keySet()); + while (!newGroupDNs.isEmpty()) { + Set newThisRound = new HashSet(); // Stores the groups we find in this iteration + + for (String groupDN : newGroupDNs) { + filter = roleFormat.format(new String[] { groupDN }); + + if (containerLog.isTraceEnabled()) { + containerLog.trace("Perform a nested group search with base "+ roleBase + " and filter " + filter); + } + + results = context.search(roleBase, filter, controls); + + try { + while (results.hasMore()) { + SearchResult result = results.next(); + Attributes attrs = result.getAttributes(); + if (attrs == null) + continue; + String dname = getDistinguishedName(context, roleBase, result); + String name = getAttributeValue(roleName, attrs); + if (name != null && dname != null && !groupMap.keySet().contains(dname)) { + groupMap.put(dname, name); + newThisRound.add(dname); + + if (containerLog.isTraceEnabled()) { + containerLog.trace(" Found nested role " + dname + " -> " + name); + } + + } + } + } catch (PartialResultException ex) { + if (!adCompat) + throw ex; + } + } + + newGroupDNs = newThisRound; + } + } - HashSet recursiveSet = new HashSet(); - HashMap recursiveMap = new HashMap(); - - for (Iterator i = keys.iterator(); i.hasNext();) { - String k = i.next(); - getRolesRecursive(0, context, recursiveMap, recursiveSet, groupMap.get(k), k); - } - - HashSet resultSet = new HashSet(list); - resultSet.addAll(recursiveMap.values()); - - if (containerLog.isTraceEnabled()) { - containerLog.trace(" Returning " + resultSet.size() + " roles"); - for (Iterator i = resultSet.iterator(); i.hasNext();) - containerLog.trace( " Found role " + i.next()); - } - - return new ArrayList(resultSet); + return new ArrayList(groupMap.values()); }