Created
October 16, 2014 22:14
-
-
Save adamfranco/c88f582694b8dddcfef7 to your computer and use it in GitHub Desktop.
Ancestor Group support for CAS 3.x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 [email protected] | |
* @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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- 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