|
@@ -0,0 +1,285 @@
|
|
|
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
|
|
|
+ *
|
|
|
+ * Licensed 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.acegisecurity.providers.ldap.populator;
|
|
|
+
|
|
|
+import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
|
|
|
+import org.acegisecurity.providers.ldap.LdapDataAccessException;
|
|
|
+import org.acegisecurity.providers.ldap.InitialDirContextFactory;
|
|
|
+import org.acegisecurity.providers.ldap.LdapUtils;
|
|
|
+import org.acegisecurity.GrantedAuthority;
|
|
|
+import org.acegisecurity.GrantedAuthorityImpl;
|
|
|
+import org.apache.commons.logging.Log;
|
|
|
+import org.apache.commons.logging.LogFactory;
|
|
|
+import org.springframework.util.Assert;
|
|
|
+import org.springframework.beans.factory.InitializingBean;
|
|
|
+
|
|
|
+import javax.naming.directory.Attributes;
|
|
|
+import javax.naming.directory.Attribute;
|
|
|
+import javax.naming.directory.SearchControls;
|
|
|
+import javax.naming.directory.SearchResult;
|
|
|
+import javax.naming.directory.DirContext;
|
|
|
+import javax.naming.NamingEnumeration;
|
|
|
+import javax.naming.NamingException;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.HashSet;
|
|
|
+
|
|
|
+/**
|
|
|
+ * The default strategy for obtaining user role information from the directory.
|
|
|
+ * <p>
|
|
|
+ * It obtains roles by
|
|
|
+ * <ul>
|
|
|
+ * <li>Reading the values of the roles specified by the attribute names in the
|
|
|
+ * <tt>userRoleAttributes</tt> </li>
|
|
|
+ * <li>Performing a search for "groups" the user is a member of and adding
|
|
|
+ * those to the list of roles.</li>
|
|
|
+ * </ul>
|
|
|
+ * </p>
|
|
|
+ * <p>
|
|
|
+ * If the <tt>userRolesAttributes</tt> property is set, any matching
|
|
|
+ * attributes amongst those retrieved for the user will have their values added
|
|
|
+ * to the list of roles.
|
|
|
+ * If <tt>userRolesAttributes</tt> is null, no attributes will be mapped to roles.
|
|
|
+ * </p>
|
|
|
+ * <p>
|
|
|
+ * A typical group search scenario would be where each group/role is specified using
|
|
|
+ * the <tt>groupOfNames</tt> (or <tt>groupOfUniqueNames</tt>) LDAP objectClass
|
|
|
+ * and the user's DN is listed in the <tt>member</tt> (or <tt>uniqueMember</tt>) attribute
|
|
|
+ * to indicate that they should be assigned that role. The following LDIF sample
|
|
|
+ * has the groups stored under the DN <tt>ou=groups,dc=acegisecurity,dc=org</tt>
|
|
|
+ * and a group called "developers" with "ben" and "marissa" as members:
|
|
|
+ *
|
|
|
+ * <pre>
|
|
|
+ * dn: ou=groups,dc=acegisecurity,dc=org
|
|
|
+ * objectClass: top
|
|
|
+ * objectClass: organizationalUnit
|
|
|
+ * ou: groups
|
|
|
+ *
|
|
|
+ * dn: cn=developers,ou=groups,dc=acegisecurity,dc=org
|
|
|
+ * objectClass: groupOfNames
|
|
|
+ * objectClass: top
|
|
|
+ * cn: developers
|
|
|
+ * description: Acegi Security Developers
|
|
|
+ * member: uid=ben,ou=people,dc=acegisecurity,dc=org
|
|
|
+ * member: uid=marissa,ou=people,dc=acegisecurity,dc=org
|
|
|
+ * ou: developer
|
|
|
+ * </pre>
|
|
|
+ * </p>
|
|
|
+ * <p>
|
|
|
+ * The group search is performed within a DN specified by the <tt>groupSearchBase</tt>
|
|
|
+ * property, which should be relative to the root DN of its <tt>InitialDirContextFactory</tt>.
|
|
|
+ * If the search base is null, group searching is disabled. The filter used in the search is defined by the
|
|
|
+ * <tt>groupSearchFilter</tt> property, with the filter argument {0} being the full DN of the user. You can also specify which attribute defines the role name by
|
|
|
+ * setting the <tt>groupRoleAttribute</tt> property (the default is "cn").
|
|
|
+ * </p>
|
|
|
+ * <p>
|
|
|
+ * <bean id="ldapAuthoritiesPopulator" class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
|
|
|
+ * TODO
|
|
|
+ * </bean>
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * @author Luke Taylor
|
|
|
+ * @version $Id$
|
|
|
+ */
|
|
|
+public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator,
|
|
|
+ InitializingBean {
|
|
|
+ //~ Static fields/initializers =============================================
|
|
|
+
|
|
|
+ private static final Log logger = LogFactory.getLog(DefaultLdapAuthoritiesPopulator.class);
|
|
|
+
|
|
|
+ //~ Instance fields ========================================================
|
|
|
+
|
|
|
+ /** Attributes of the User's LDAP Object that contain role name information. */
|
|
|
+ private String[] userRoleAttributes = null;
|
|
|
+
|
|
|
+ private String rolePrefix = "";
|
|
|
+
|
|
|
+ /** The base DN from which the search for group membership should be performed */
|
|
|
+ private String groupSearchBase = null;
|
|
|
+
|
|
|
+ /** The pattern to be used for the user search. {0} is the user's DN */
|
|
|
+ private String groupSearchFilter = "(member={0})";
|
|
|
+
|
|
|
+ /** The ID of the attribute which contains the role name for a group */
|
|
|
+ private String groupRoleAttribute = "cn";
|
|
|
+
|
|
|
+ /** Whether group searches should be performed over the full sub-tree from the base DN */
|
|
|
+ // private boolean searchSubtree = false;
|
|
|
+
|
|
|
+ /** Internal variable, tied to searchSubTree property */
|
|
|
+ private int searchScope = SearchControls.ONELEVEL_SCOPE;
|
|
|
+
|
|
|
+ private boolean convertToUpperCase = true;
|
|
|
+
|
|
|
+ /** An initial context factory is only required if searching for groups is required. */
|
|
|
+ private InitialDirContextFactory initialDirContextFactory = null;
|
|
|
+
|
|
|
+ //~ Methods ================================================================
|
|
|
+
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * @param username the login name passed to the authentication provider.
|
|
|
+ * @param userDn the user's DN.
|
|
|
+ * @param userAttributes the attributes retrieved from the user's directory entry.
|
|
|
+ * @return the full set of roles granted to the user.
|
|
|
+ */
|
|
|
+ public GrantedAuthority[] getGrantedAuthorities(String username, String userDn, Attributes userAttributes) {
|
|
|
+ logger.debug("Getting authorities for user " + userDn);
|
|
|
+
|
|
|
+ Set roles = getRolesFromUserAttributes(userDn, userAttributes);
|
|
|
+
|
|
|
+ Set groupRoles = getGroupMembershipRoles(userDn, userAttributes);
|
|
|
+
|
|
|
+ if(groupRoles != null) {
|
|
|
+ roles.addAll(groupRoles);
|
|
|
+ }
|
|
|
+
|
|
|
+ return (GrantedAuthority[])roles.toArray(new GrantedAuthority[roles.size()]);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected Set getRolesFromUserAttributes(String userDn, Attributes userAttributes) {
|
|
|
+ Set userRoles = new HashSet();
|
|
|
+
|
|
|
+ for(int i=0; userRoleAttributes != null && i < userRoleAttributes.length; i++) {
|
|
|
+ Attribute roleAttribute = userAttributes.get(userRoleAttributes[i]);
|
|
|
+
|
|
|
+ addAttributeValuesToRoleSet(roleAttribute, userRoles);
|
|
|
+ }
|
|
|
+
|
|
|
+ return userRoles;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Searches for groups the user is a member of.
|
|
|
+ *
|
|
|
+ * @param userDn the user's distinguished name.
|
|
|
+ * @param userAttributes
|
|
|
+ * @return the set of roles obtained from a group membership search.
|
|
|
+ */
|
|
|
+ protected Set getGroupMembershipRoles(String userDn, Attributes userAttributes) {
|
|
|
+ Set userRoles = new HashSet();
|
|
|
+
|
|
|
+ if (groupSearchBase == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ DirContext ctx = initialDirContextFactory.newInitialDirContext();
|
|
|
+ SearchControls ctls = new SearchControls();
|
|
|
+
|
|
|
+ ctls.setSearchScope(searchScope);
|
|
|
+ ctls.setReturningAttributes(new String[] {groupRoleAttribute});
|
|
|
+
|
|
|
+ try {
|
|
|
+ NamingEnumeration groups =
|
|
|
+ ctx.search(groupSearchBase, groupSearchFilter, new String[]{userDn}, ctls);
|
|
|
+
|
|
|
+ while (groups.hasMore()) {
|
|
|
+ SearchResult result = (SearchResult) groups.next();
|
|
|
+ Attributes attrs = result.getAttributes();
|
|
|
+
|
|
|
+ // There should only be one role attribute.
|
|
|
+ NamingEnumeration groupRoleAttributes = attrs.getAll();
|
|
|
+
|
|
|
+ while(groupRoleAttributes.hasMore()) {
|
|
|
+ Attribute roleAttribute = (Attribute) groupRoleAttributes.next();
|
|
|
+
|
|
|
+ addAttributeValuesToRoleSet(roleAttribute, userRoles);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (NamingException e) {
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ LdapUtils.closeContext(ctx);
|
|
|
+ }
|
|
|
+
|
|
|
+ return userRoles;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void addAttributeValuesToRoleSet(Attribute roleAttribute, Set roles) {
|
|
|
+ if(roleAttribute == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ NamingEnumeration attributeRoles = roleAttribute.getAll();
|
|
|
+
|
|
|
+ while(attributeRoles.hasMore()) {
|
|
|
+ Object role = attributeRoles.next();
|
|
|
+
|
|
|
+ // We only handle Strings for the time being
|
|
|
+ if(role instanceof String) {
|
|
|
+ if(convertToUpperCase) {
|
|
|
+ role = ((String)role).toUpperCase();
|
|
|
+ }
|
|
|
+
|
|
|
+ roles.add(new GrantedAuthorityImpl(rolePrefix + role));
|
|
|
+ } else {
|
|
|
+ logger.warn("Non-String value found for role attribute " + roleAttribute.getID());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch(NamingException ne) {
|
|
|
+ throw new LdapDataAccessException("Error retrieving values for role attribute " +
|
|
|
+ roleAttribute.getID(), ne);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected String[] getUserRoleAttributes() {
|
|
|
+ return userRoleAttributes;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setUserRoleAttributes(String[] userRoleAttributes) {
|
|
|
+ this.userRoleAttributes = userRoleAttributes;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setRolePrefix(String rolePrefix) {
|
|
|
+ Assert.notNull(rolePrefix, "rolePrefix must not be null");
|
|
|
+ this.rolePrefix = rolePrefix;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setGroupSearchBase(String groupSearchBase) {
|
|
|
+ this.groupSearchBase = groupSearchBase;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setGroupSearchFilter(String groupSearchFilter) {
|
|
|
+ Assert.notNull(groupSearchFilter, "groupSearchFilter must not be null");
|
|
|
+ this.groupSearchFilter = groupSearchFilter;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setGroupRoleAttribute(String groupRoleAttribute) {
|
|
|
+ Assert.notNull(groupRoleAttribute, "groupRoleAttribute must not be null");
|
|
|
+ this.groupRoleAttribute = groupRoleAttribute;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setSearchSubtree(boolean searchSubtree) {
|
|
|
+ // this.searchSubtree = searchSubtree;
|
|
|
+ this.searchScope = searchSubtree ?
|
|
|
+ SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setConvertToUpperCase(boolean convertToUpperCase) {
|
|
|
+ this.convertToUpperCase = convertToUpperCase;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) {
|
|
|
+ this.initialDirContextFactory = initialDirContextFactory;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void afterPropertiesSet() throws Exception {
|
|
|
+ if(initialDirContextFactory == null && groupSearchBase != null) {
|
|
|
+ throw new IllegalArgumentException("initialDirContextFactory is required for group role searches.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|