Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save adamfranco/c88f582694b8dddcfef7 to your computer and use it in GitHub Desktop.
Save adamfranco/c88f582694b8dddcfef7 to your computer and use it in GitHub Desktop.
Ancestor Group support for CAS 3.x
/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you 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 org.jasig.services.persondir.support.ldap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.TreeSet;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.collections.map.CaseInsensitiveMap;
import org.jasig.services.persondir.IPersonAttributes;
import org.jasig.services.persondir.support.CaseInsensitiveAttributeNamedPersonImpl;
import org.jasig.services.persondir.support.CaseInsensitiveNamedPersonImpl;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This LDAP implementation of the DAO recursively searches groups when encountering
* a memberOf attribute.
*
* @author afranco@middlebury.edu
* @author Adam Franco
*/
public class AncestorGroupsLdapPersonAttributeDao extends LdapPersonAttributeDao {
protected final Log logger = LogFactory.getLog(this.getClass());
/**
* The LdapTemplate to use to execute queries on the DirContext
*/
private LdapTemplate ldapTemplate = null;
/**
* @param contextSource The ContextSource to get DirContext objects for queries from.
*/
public synchronized void setContextSource(final ContextSource contextSource) {
super.setContextSource(contextSource);
// Store a template locally for our use.
this.ldapTemplate = new LdapTemplate(contextSource);
}
/**
* Sets the LdapTemplate, and thus the ContextSource (implicitly).
*
* @param ldapTemplate the LdapTemplate to query the LDAP server from. CANNOT be NULL.
*/
public synchronized void setLdapTemplate(final LdapTemplate ldapTemplate) {
super.setLdapTemplate(ldapTemplate);
// Store a template locally for our use.
this.ldapTemplate = ldapTemplate;
}
/**
* Override the LdapPersonAttributeDao's implementation to add ancestor groups
* based on traversing the memberOf hierarchy.
*
*(non-Javadoc)
* @see org.jasig.services.persondir.support.AbstractQueryPersonAttributeDao#getPeopleForQuery(java.lang.Object, java.lang.String)
*/
@Override
protected List<IPersonAttributes> getPeopleForQuery(LogicalFilterWrapper queryBuilder, String queryUserName) {
final List<IPersonAttributes> people = super.getPeopleForQuery(queryBuilder, queryUserName);
// Create a new list for our people with additional groups.
final List<IPersonAttributes> myPeople = new ArrayList<IPersonAttributes>(people.size());
for (IPersonAttributes person : people) {
final Map<String, List<Object>> attributes = person.getAttributes();
// If the person has memberOf attributes, look for ancestor groups
// and add them to the attributes.
if (attributes.containsKey("memberOf")) {
final Map<String, List<Object>> myAttributes = this.getAttributesWithAncestorGroups(attributes);
// Create the new person object with the updated attributes.
final IPersonAttributes myPerson;
if (queryUserName != null) {
myPerson = new CaseInsensitiveNamedPersonImpl(queryUserName, myAttributes);
}
else {
//Create the IPersonAttributes doing a best-guess at a userName attribute
final String userNameAttribute = this.getConfiguredUserNameAttribute();
myPerson = new CaseInsensitiveAttributeNamedPersonImpl(userNameAttribute, myAttributes);
}
// Add our new, updated person to our list
myPeople.add(myPerson);
}
// If this person isn't a member, just add them to our List directly
else {
myPeople.add(person);
}
}
return myPeople;
}
/**
* Answer an attribute map that contains all attributes in the input set and
* with ancestor groups added to the memberOf value.
*/
public Map<String, List<Object>> getAttributesWithAncestorGroups(Map<String, List<Object>> attributes) {
final Map<String, List<Object>> myAttributes = new CaseInsensitiveMap(attributes.size());
// Add the original attributes to our attributes.
for (String key : attributes.keySet()) {
// Search for groups
if (key.equalsIgnoreCase("memberOf")) {
// Compile a unique set of groups.
Set<String> allGroups = new TreeSet();
final List<Object> directGroups = attributes.get(key);
for (Object directGroup : directGroups) {
// Add the direct group.
allGroups.add(directGroup.toString());
// Add any ancestor groups
Set<String> ancestorGroups = this.getAncestorGroups(directGroup.toString());
this.logger.info("Found " + ancestorGroups.size() + " ancestor groups of " + directGroup);
allGroups.addAll(ancestorGroups);
}
// Convert our unique set to a list.
final List<Object> myGroups = new ArrayList<Object>(allGroups);
myAttributes.put(key, myGroups);
}
// Add other attributes as-is
else {
myAttributes.put(key, attributes.get(key));
}
}
return myAttributes;
}
/**
* Answer the ancestor groups that a groupDN is a member of.
*/
public Set<String> getAncestorGroups(String childName) {
return this.getAncestorGroups(childName, new HashSet());
}
/**
* Answer the ancestor groups that a groupDN is a member of.
*/
public Set<String> getAncestorGroups(String childName, Set<String> explored) {
Assert.notNull(this.ldapTemplate, "ldapTemplate can not be null");
Set<String> ancestors = new HashSet();
// Escape if there are cycles in the groups
if (explored.contains(childName)) {
return ancestors;
}
// Add our name to the list of explored to prevent entrapment in cycles.
explored.add(childName);
String[] attrs = {"memberOf"};
// Forward Slashes need to be escaped in DNs, manually escape them.
if (childName.contains("/")) {
this.logger.debug("Found a / in the DN '" + childName + "', escaping with \\");
childName = childName.replace("/", "\\/");
}
Set<String> parents = (Set<String>) this.ldapTemplate.lookup(childName, attrs, new MemberOfAttributesMapper());
for (final String parent : parents) {
ancestors.add(parent);
// Recursivly add up the directory
Set<String> grandparentsAndOlder = this.getAncestorGroups(parent, explored);
for (final String grandparentOrOlder : grandparentsAndOlder) {
ancestors.add(grandparentOrOlder);
}
}
return ancestors;
}
}
/* Copyright 2006 The JA-SIG Collaborative. All rights reserved.
* See license distributed with this file and
* available online at http://www.uportal.org/license.html
*/
package org.jasig.services.persondir.support.ldap;
import java.util.Collections;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapTemplate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.services.persondir.support.MultivaluedPersonAttributeUtils;
import org.springframework.ldap.core.AttributesMapper;
/**
* Provides {@link net.sf.ldaptemplate.AttributesMapper} for use with a {@link net.sf.ldaptemplate.LdapTemplate}
* to parse ldap query results into the person attribute Map format.
*
* @author Eric Dalquist
* @version $Revision$
*/
class MemberOfAttributesMapper implements AttributesMapper {
protected final Log logger = LogFactory.getLog(this.getClass());
/**
* Create a mapper with the ldap to portal attribute mappings. Please read the
* documentation for {@link org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao#setLdapAttributesToPortalAttributes(Map)}
*
* @param ldapAttributesToPortalAttributes Map of ldap to portal attributes.
* @see org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao#setLdapAttributesToPortalAttributes(Map)
*/
public MemberOfAttributesMapper() {
}
/**
* Performs mapping after an LDAP query for a set of user attributes. Takes each key in the ldap
* to portal attribute Map and tries to find it in the returned Attributes set. For each found
* Attribute the value is added to the attribute Map as the value or in the value Set with the
* portal attribute name as the key. String and byte[] may be values.
*
* @see net.sf.ldaptemplate.AttributesMapper#mapFromAttributes(javax.naming.directory.Attributes)
*/
public Object mapFromAttributes(Attributes attributes) throws NamingException {
Set ancestors = new HashSet();
// The attribute exists
final Attribute attribute = attributes.get("memberOf");
if (attribute != null) {
int valueCount = 0;
for (final NamingEnumeration<?> attrValueEnum = attribute.getAll(); attrValueEnum.hasMore(); valueCount++) {
Object attributeValue = attrValueEnum.next();
// Convert byte[] to String
if ((attributeValue instanceof byte[])) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Converting value " + valueCount + " of LDAP attribute 'memberOf' from byte[] to String");
}
attributeValue = attributeValue.toString();
}
ancestors.add(attributeValue);
}
}
return ancestors;
}
}
--- a/src/main/webapp/WEB-INF/deployerConfigContext.xml
+++ b/src/main/webapp/WEB-INF/deployerConfigContext.xml
@@ -180,7 +180,7 @@
-->
<bean id="attributeRepository"
- class="org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao">
+ class="org.jasig.services.persondir.support.ldap.AncestorGroupsLdapPersonAttributeDao">
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment