001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.providers.ldap.populator;
017:
018: import org.acegisecurity.GrantedAuthority;
019: import org.acegisecurity.GrantedAuthorityImpl;
020:
021: import org.acegisecurity.ldap.InitialDirContextFactory;
022: import org.acegisecurity.ldap.LdapTemplate;
023:
024: import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
025:
026: import org.acegisecurity.userdetails.ldap.LdapUserDetails;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030:
031: import org.springframework.util.Assert;
032:
033: import java.util.HashSet;
034: import java.util.Iterator;
035: import java.util.Set;
036:
037: import javax.naming.directory.Attributes;
038: import javax.naming.directory.SearchControls;
039:
040: /**
041: * The default strategy for obtaining user role information from the directory.
042: * <p/>
043: * <p>It obtains roles by performing a search for "groups" the user is a member of.</p>
044: * <p/>
045: * <p/>
046: * A typical group search scenario would be where each group/role is specified using the <tt>groupOfNames</tt>
047: * (or <tt>groupOfUniqueNames</tt>) LDAP objectClass and the user's DN is listed in the <tt>member</tt> (or
048: * <tt>uniqueMember</tt>) attribute to indicate that they should be assigned that role. The following LDIF sample has
049: * the groups stored under the DN <tt>ou=groups,dc=acegisecurity,dc=org</tt> and a group called "developers" with
050: * "ben" and "marissa" as members:
051: * <pre>
052: * dn: ou=groups,dc=acegisecurity,dc=orgobjectClass: top
053: * objectClass: organizationalUnitou: groupsdn: cn=developers,ou=groups,dc=acegisecurity,dc=org
054: * objectClass: groupOfNamesobjectClass: topcn: developersdescription: Acegi Security Developers
055: * member: uid=ben,ou=people,dc=acegisecurity,dc=orgmember: uid=marissa,ou=people,dc=acegisecurity,dc=orgou: developer
056: * </pre>
057: * </p>
058: * <p/>
059: * The group search is performed within a DN specified by the <tt>groupSearchBase</tt> property, which should
060: * be relative to the root DN of its <tt>InitialDirContextFactory</tt>. If the search base is null, group searching is
061: * disabled. The filter used in the search is defined by the <tt>groupSearchFilter</tt> property, with the filter
062: * argument {0} being the full DN of the user. You can also optionally use the parameter {1}, which will be substituted
063: * with the username. You can also specify which attribute defines the role name by setting
064: * the <tt>groupRoleAttribute</tt> property (the default is "cn").</p>
065: * <p/>
066: * <p>The configuration below shows how the group search might be performed with the above schema.
067: * <pre>
068: * <bean id="ldapAuthoritiesPopulator"
069: * class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
070: * <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
071: * <constructor-arg><value>ou=groups</value></constructor-arg>
072: * <property name="groupRoleAttribute"><value>ou</value></property>
073: * <!-- the following properties are shown with their default values -->
074: * <property name="searchSubTree"><value>false</value></property>
075: * <property name="rolePrefix"><value>ROLE_</value></property>
076: * <property name="convertToUpperCase"><value>true</value></property>
077: * </bean>
078: * </pre>
079: * A search for roles for user "uid=ben,ou=people,dc=acegisecurity,dc=org" would return the single granted authority
080: * "ROLE_DEVELOPER".
081: * </p>
082: * <p/>
083: * The single-level search is performed by default. Setting the <tt>searchSubTree</tt> property to true will enable
084: * a search of the entire subtree under <tt>groupSearchBase</tt>.
085: *
086: * @author Luke Taylor
087: * @version $Id: DefaultLdapAuthoritiesPopulator.java 1996 2007-08-30 21:15:14Z luke_t $
088: */
089: public class DefaultLdapAuthoritiesPopulator implements
090: LdapAuthoritiesPopulator {
091: //~ Static fields/initializers =====================================================================================
092:
093: private static final Log logger = LogFactory
094: .getLog(DefaultLdapAuthoritiesPopulator.class);
095:
096: //~ Instance fields ================================================================================================
097:
098: /**
099: * A default role which will be assigned to all authenticated users if set
100: */
101: private GrantedAuthority defaultRole = null;
102:
103: /**
104: * An initial context factory is only required if searching for groups is required.
105: */
106: private InitialDirContextFactory initialDirContextFactory = null;
107: private LdapTemplate ldapTemplate;
108:
109: /**
110: * Controls used to determine whether group searches should be performed over the full sub-tree from the
111: * base DN. Modified by searchSubTree property
112: */
113: private SearchControls searchControls = new SearchControls();
114:
115: /**
116: * The ID of the attribute which contains the role name for a group
117: */
118: private String groupRoleAttribute = "cn";
119:
120: /**
121: * The base DN from which the search for group membership should be performed
122: */
123: private String groupSearchBase = null;
124:
125: /**
126: * The pattern to be used for the user search. {0} is the user's DN
127: */
128: private String groupSearchFilter = "(member={0})";
129:
130: /**
131: * Attributes of the User's LDAP Object that contain role name information.
132: */
133:
134: // private String[] userRoleAttributes = null;
135: private String rolePrefix = "ROLE_";
136: private boolean convertToUpperCase = true;
137:
138: //~ Constructors ===================================================================================================
139:
140: /**
141: * Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be
142: * set as a property.
143: *
144: * @param initialDirContextFactory supplies the contexts used to search for user roles.
145: * @param groupSearchBase if this is an empty string the search will be performed from the root DN of the
146: * context factory.
147: */
148: public DefaultLdapAuthoritiesPopulator(
149: InitialDirContextFactory initialDirContextFactory,
150: String groupSearchBase) {
151: this .setInitialDirContextFactory(initialDirContextFactory);
152: this .setGroupSearchBase(groupSearchBase);
153: }
154:
155: //~ Methods ========================================================================================================
156:
157: /**
158: * This method should be overridden if required to obtain any additional
159: * roles for the given user (on top of those obtained from the standard
160: * search implemented by this class).
161: *
162: * @param ldapUser the user who's roles are required
163: * @return the extra roles which will be merged with those returned by the group search
164: */
165:
166: protected Set getAdditionalRoles(LdapUserDetails ldapUser) {
167: return null;
168: }
169:
170: /**
171: * Obtains the authorities for the user who's directory entry is represented by
172: * the supplied LdapUserDetails object.
173: *
174: * @param userDetails the user who's authorities are required
175: * @return the set of roles granted to the user.
176: */
177: public final GrantedAuthority[] getGrantedAuthorities(
178: LdapUserDetails userDetails) {
179: String userDn = userDetails.getDn();
180:
181: if (logger.isDebugEnabled()) {
182: logger.debug("Getting authorities for user " + userDn);
183: }
184:
185: Set roles = getGroupMembershipRoles(userDn, userDetails
186: .getUsername());
187:
188: // Temporary use of deprecated method
189: Set oldGroupRoles = getGroupMembershipRoles(userDn, userDetails
190: .getAttributes());
191:
192: if (oldGroupRoles != null) {
193: roles.addAll(oldGroupRoles);
194: }
195:
196: Set extraRoles = getAdditionalRoles(userDetails);
197:
198: if (extraRoles != null) {
199: roles.addAll(extraRoles);
200: }
201:
202: if (defaultRole != null) {
203: roles.add(defaultRole);
204: }
205:
206: return (GrantedAuthority[]) roles
207: .toArray(new GrantedAuthority[roles.size()]);
208: }
209:
210: // protected Set getRolesFromUserAttributes(String userDn, Attributes userAttributes) {
211: // Set userRoles = new HashSet();
212: //
213: // for(int i=0; userRoleAttributes != null && i < userRoleAttributes.length; i++) {
214: // Attribute roleAttribute = userAttributes.get(userRoleAttributes[i]);
215: //
216: // addAttributeValuesToRoleSet(roleAttribute, userRoles);
217: // }
218: //
219: // return userRoles;
220: // }
221:
222: public Set getGroupMembershipRoles(String userDn, String username) {
223: Set authorities = new HashSet();
224:
225: if (getGroupSearchBase() == null) {
226: return authorities;
227: }
228:
229: if (logger.isDebugEnabled()) {
230: logger.debug("Searching for roles for user '" + username
231: + "', DN = " + "'" + userDn + "', with filter "
232: + groupSearchFilter + " in search base '"
233: + getGroupSearchBase() + "'");
234: }
235:
236: Set userRoles = ldapTemplate.searchForSingleAttributeValues(
237: getGroupSearchBase(), groupSearchFilter, new String[] {
238: userDn, username }, groupRoleAttribute);
239:
240: if (logger.isDebugEnabled()) {
241: logger.debug("Roles from search: " + userRoles);
242: }
243:
244: Iterator it = userRoles.iterator();
245:
246: while (it.hasNext()) {
247: String role = (String) it.next();
248:
249: if (convertToUpperCase) {
250: role = role.toUpperCase();
251: }
252:
253: authorities
254: .add(new GrantedAuthorityImpl(rolePrefix + role));
255: }
256:
257: return authorities;
258: }
259:
260: /**
261: * Searches for groups the user is a member of.
262: *
263: * @param userDn the user's distinguished name.
264: * @param userAttributes the retrieved user's attributes (unused by default).
265: * @return the set of roles obtained from a group membership search, or null if <tt>groupSearchBase</tt> has been
266: * set.
267: * @deprecated Subclasses should implement <tt>getAdditionalRoles</tt> instead.
268: */
269: protected Set getGroupMembershipRoles(String userDn,
270: Attributes userAttributes) {
271: return new HashSet();
272: }
273:
274: protected InitialDirContextFactory getInitialDirContextFactory() {
275: return initialDirContextFactory;
276: }
277:
278: /**
279: * Set the {@link InitialDirContextFactory}
280: *
281: * @param initialDirContextFactory supplies the contexts used to search for user roles.
282: */
283: private void setInitialDirContextFactory(
284: InitialDirContextFactory initialDirContextFactory) {
285: Assert.notNull(initialDirContextFactory,
286: "InitialDirContextFactory must not be null");
287: this .initialDirContextFactory = initialDirContextFactory;
288:
289: ldapTemplate = new LdapTemplate(initialDirContextFactory);
290: ldapTemplate.setSearchControls(searchControls);
291: }
292:
293: /**
294: * Set the group search base (name to search under)
295: *
296: * @param groupSearchBase if this is an empty string the search will be performed from the root DN of the context
297: * factory.
298: */
299: private void setGroupSearchBase(String groupSearchBase) {
300: Assert
301: .notNull(groupSearchBase,
302: "The groupSearchBase (name to search under), must not be null.");
303: this .groupSearchBase = groupSearchBase;
304: if (groupSearchBase.length() == 0) {
305: logger
306: .info("groupSearchBase is empty. Searches will be performed from the root: "
307: + getInitialDirContextFactory().getRootDn());
308: }
309: }
310:
311: protected String getGroupSearchBase() {
312: return groupSearchBase;
313: }
314:
315: public void setConvertToUpperCase(boolean convertToUpperCase) {
316: this .convertToUpperCase = convertToUpperCase;
317: }
318:
319: /**
320: * The default role which will be assigned to all users.
321: *
322: * @param defaultRole the role name, including any desired prefix.
323: */
324: public void setDefaultRole(String defaultRole) {
325: Assert.notNull(defaultRole,
326: "The defaultRole property cannot be set to null");
327: this .defaultRole = new GrantedAuthorityImpl(defaultRole);
328: }
329:
330: public void setGroupRoleAttribute(String groupRoleAttribute) {
331: Assert.notNull(groupRoleAttribute,
332: "groupRoleAttribute must not be null");
333: this .groupRoleAttribute = groupRoleAttribute;
334: }
335:
336: public void setGroupSearchFilter(String groupSearchFilter) {
337: Assert.notNull(groupSearchFilter,
338: "groupSearchFilter must not be null");
339: this .groupSearchFilter = groupSearchFilter;
340: }
341:
342: public void setRolePrefix(String rolePrefix) {
343: Assert.notNull(rolePrefix, "rolePrefix must not be null");
344: this .rolePrefix = rolePrefix;
345: }
346:
347: /**
348: * If set to true, a subtree scope search will be performed. If false a single-level search is used.
349: *
350: * @param searchSubtree set to true to enable searching of the entire tree below the <tt>groupSearchBase</tt>.
351: */
352: public void setSearchSubtree(boolean searchSubtree) {
353: int searchScope = searchSubtree ? SearchControls.SUBTREE_SCOPE
354: : SearchControls.ONELEVEL_SCOPE;
355: searchControls.setSearchScope(searchScope);
356: }
357: }
|