View Javadoc

1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.providers.ldap.populator;
17  
18  import org.acegisecurity.GrantedAuthority;
19  import org.acegisecurity.GrantedAuthorityImpl;
20  
21  import org.acegisecurity.ldap.InitialDirContextFactory;
22  import org.acegisecurity.ldap.LdapTemplate;
23  
24  import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
25  
26  import org.acegisecurity.userdetails.ldap.LdapUserDetails;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import org.springframework.util.Assert;
32  
33  import java.util.HashSet;
34  import java.util.Iterator;
35  import java.util.Set;
36  
37  import javax.naming.directory.Attributes;
38  import javax.naming.directory.SearchControls;
39  
40  
41  /**
42   * The default strategy for obtaining user role information from the directory.
43   * <p/>
44   * <p>It obtains roles by performing a search for "groups" the user is a member of.</p>
45   * <p/>
46   * <p/>
47   * A typical group search scenario would be where each group/role is specified using the <tt>groupOfNames</tt>
48   * (or <tt>groupOfUniqueNames</tt>) LDAP objectClass and the user's DN is listed in the <tt>member</tt> (or
49   * <tt>uniqueMember</tt>) attribute to indicate that they should be assigned that role. The following LDIF sample has
50   * the groups stored under the DN <tt>ou=groups,dc=acegisecurity,dc=org</tt> and a group called "developers" with
51   * "ben" and "marissa" as members:
52   * <pre>
53   * dn: ou=groups,dc=acegisecurity,dc=orgobjectClass: top
54   * objectClass: organizationalUnitou: groupsdn: cn=developers,ou=groups,dc=acegisecurity,dc=org
55   * objectClass: groupOfNamesobjectClass: topcn: developersdescription: Acegi Security Developers
56   * member: uid=ben,ou=people,dc=acegisecurity,dc=orgmember: uid=marissa,ou=people,dc=acegisecurity,dc=orgou: developer
57   * </pre>
58   * </p>
59   * <p/>
60   * The group search is performed within a DN specified by the <tt>groupSearchBase</tt> property, which should
61   * be relative to the root DN of its <tt>InitialDirContextFactory</tt>. If the search base is null, group searching is
62   * disabled. The filter used in the search is defined by the <tt>groupSearchFilter</tt> property, with the filter
63   * argument {0} being the full DN of the user. You can also optionally use the parameter {1}, which will be substituted
64   * with the username. You can also specify which attribute defines the role name by setting
65   * the <tt>groupRoleAttribute</tt> property (the default is "cn").</p>
66   * <p/>
67   * <p>The configuration below shows how the group search might be performed with the above schema.
68   * <pre>
69   * &lt;bean id="ldapAuthoritiesPopulator"
70   *         class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
71   *   &lt;constructor-arg>&lt;ref local="initialDirContextFactory"/>&lt;/constructor-arg>
72   *   &lt;constructor-arg>&lt;value>ou=groups&lt;/value>&lt;/constructor-arg>
73   *   &lt;property name="groupRoleAttribute">&lt;value>ou&lt;/value>&lt;/property>
74   * &lt;!-- the following properties are shown with their default values -->
75   *   &lt;property name="searchSubTree">&lt;value>false&lt;/value>&lt;/property>
76   *   &lt;property name="rolePrefix">&lt;value>ROLE_&lt;/value>&lt;/property>
77   *   &lt;property name="convertToUpperCase">&lt;value>true&lt;/value>&lt;/property>
78   * &lt;/bean>
79   * </pre>
80   * A search for roles for user "uid=ben,ou=people,dc=acegisecurity,dc=org" would return the single granted authority
81   * "ROLE_DEVELOPER".
82   * </p>
83   * <p/>
84   * The single-level search is performed by default. Setting the <tt>searchSubTree</tt> property to true will enable
85   * a search of the entire subtree under <tt>groupSearchBase</tt>.
86   *
87   * @author Luke Taylor
88   * @version $Id: DefaultLdapAuthoritiesPopulator.java 1996 2007-08-30 21:15:14Z luke_t $
89   */
90  public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
91      //~ Static fields/initializers =====================================================================================
92  
93      private static final Log logger = LogFactory.getLog(DefaultLdapAuthoritiesPopulator.class);
94  
95      //~ Instance fields ================================================================================================
96  
97      /**
98       * A default role which will be assigned to all authenticated users if set
99       */
100     private GrantedAuthority defaultRole = null;
101 
102     /**
103      * An initial context factory is only required if searching for groups is required.
104      */
105     private InitialDirContextFactory initialDirContextFactory = null;
106     private LdapTemplate ldapTemplate;
107 
108     /**
109      * Controls used to determine whether group searches should be performed over the full sub-tree from the
110      * base DN. Modified by searchSubTree property
111      */
112     private SearchControls searchControls = new SearchControls();
113 
114     /**
115      * The ID of the attribute which contains the role name for a group
116      */
117     private String groupRoleAttribute = "cn";
118 
119     /**
120      * The base DN from which the search for group membership should be performed
121      */
122     private String groupSearchBase = null;
123 
124     /**
125      * The pattern to be used for the user search. {0} is the user's DN
126      */
127     private String groupSearchFilter = "(member={0})";
128 
129     /**
130      * Attributes of the User's LDAP Object that contain role name information.
131      */
132 
133 //    private String[] userRoleAttributes = null;
134     private String rolePrefix = "ROLE_";
135     private boolean convertToUpperCase = true;
136 
137     //~ Constructors ===================================================================================================
138 
139     /**
140      * Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be
141      * set as a property.
142      *
143      * @param initialDirContextFactory supplies the contexts used to search for user roles.
144      * @param groupSearchBase          if this is an empty string the search will be performed from the root DN of the
145      *                                 context factory.
146      */
147     public DefaultLdapAuthoritiesPopulator(InitialDirContextFactory initialDirContextFactory, String groupSearchBase) {
148         this.setInitialDirContextFactory(initialDirContextFactory);
149         this.setGroupSearchBase(groupSearchBase);
150     }
151 
152     //~ Methods ========================================================================================================
153 
154     /**
155      * This method should be overridden if required to obtain any additional
156      * roles for the given user (on top of those obtained from the standard
157      * search implemented by this class).
158      *
159      * @param ldapUser the user who's roles are required
160      * @return the extra roles which will be merged with those returned by the group search
161      */
162 
163     protected Set getAdditionalRoles(LdapUserDetails ldapUser) {
164         return null;
165     }
166 
167     /**
168      * Obtains the authorities for the user who's directory entry is represented by
169      * the supplied LdapUserDetails object.
170      *
171      * @param userDetails the user who's authorities are required
172      * @return the set of roles granted to the user.
173      */
174     public final GrantedAuthority[] getGrantedAuthorities(LdapUserDetails userDetails) {
175         String userDn = userDetails.getDn();
176 
177         if (logger.isDebugEnabled()) {
178             logger.debug("Getting authorities for user " + userDn);
179         }
180 
181         Set roles = getGroupMembershipRoles(userDn, userDetails.getUsername());
182 
183         // Temporary use of deprecated method
184         Set oldGroupRoles = getGroupMembershipRoles(userDn, userDetails.getAttributes());
185 
186         if (oldGroupRoles != null) {
187             roles.addAll(oldGroupRoles);
188         }
189 
190         Set extraRoles = getAdditionalRoles(userDetails);
191 
192         if (extraRoles != null) {
193             roles.addAll(extraRoles);
194         }
195 
196         if (defaultRole != null) {
197             roles.add(defaultRole);
198         }
199 
200         return (GrantedAuthority[]) roles.toArray(new GrantedAuthority[roles.size()]);
201     }
202 
203 //    protected Set getRolesFromUserAttributes(String userDn, Attributes userAttributes) {
204 //        Set userRoles = new HashSet();
205 //
206 //        for(int i=0; userRoleAttributes != null && i < userRoleAttributes.length; i++) {
207 //            Attribute roleAttribute = userAttributes.get(userRoleAttributes[i]);
208 //
209 //            addAttributeValuesToRoleSet(roleAttribute, userRoles);
210 //        }
211 //
212 //        return userRoles;
213 //    }
214 
215 
216     public Set getGroupMembershipRoles(String userDn, String username) {
217         Set authorities = new HashSet();
218 
219         if (getGroupSearchBase() == null) {
220             return authorities;
221         }
222 
223         if (logger.isDebugEnabled()) {
224             logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
225                     + groupSearchFilter + " in search base '" + getGroupSearchBase() + "'");
226         }
227 
228         Set userRoles = ldapTemplate.searchForSingleAttributeValues(getGroupSearchBase(), groupSearchFilter,
229                 new String[]{userDn, username}, groupRoleAttribute);
230 
231         if (logger.isDebugEnabled()) {
232             logger.debug("Roles from search: " + userRoles);
233         }
234 
235         Iterator it = userRoles.iterator();
236 
237         while (it.hasNext()) {
238             String role = (String) it.next();
239 
240             if (convertToUpperCase) {
241                 role = role.toUpperCase();
242             }
243 
244             authorities.add(new GrantedAuthorityImpl(rolePrefix + role));
245         }
246 
247         return authorities;
248     }
249 
250     /**
251      * Searches for groups the user is a member of.
252      *
253      * @param userDn         the user's distinguished name.
254      * @param userAttributes the retrieved user's attributes (unused by default).
255      * @return the set of roles obtained from a group membership search, or null if <tt>groupSearchBase</tt> has been
256      *         set.
257      * @deprecated Subclasses should implement <tt>getAdditionalRoles</tt> instead.
258      */
259     protected Set getGroupMembershipRoles(String userDn, Attributes userAttributes) {
260         return new HashSet();
261     }
262 
263     protected InitialDirContextFactory getInitialDirContextFactory() {
264         return initialDirContextFactory;
265     }
266 
267     /**
268      * Set the {@link InitialDirContextFactory}
269      *
270      * @param initialDirContextFactory supplies the contexts used to search for user roles.
271      */
272     private void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) {
273         Assert.notNull(initialDirContextFactory, "InitialDirContextFactory must not be null");
274         this.initialDirContextFactory = initialDirContextFactory;
275 
276         ldapTemplate = new LdapTemplate(initialDirContextFactory);
277         ldapTemplate.setSearchControls(searchControls);
278     }
279 
280     /**
281      * Set the group search base (name to search under)
282      *
283      * @param groupSearchBase if this is an empty string the search will be performed from the root DN of the context
284      *                        factory.
285      */
286     private void setGroupSearchBase(String groupSearchBase) {
287         Assert.notNull(groupSearchBase, "The groupSearchBase (name to search under), must not be null.");
288         this.groupSearchBase = groupSearchBase;
289         if (groupSearchBase.length() == 0) {
290             logger.info("groupSearchBase is empty. Searches will be performed from the root: "
291                     + getInitialDirContextFactory().getRootDn());
292         }
293     }
294 
295     protected String getGroupSearchBase() {
296         return groupSearchBase;
297     }
298 
299     public void setConvertToUpperCase(boolean convertToUpperCase) {
300         this.convertToUpperCase = convertToUpperCase;
301     }
302 
303     /**
304      * The default role which will be assigned to all users.
305      *
306      * @param defaultRole the role name, including any desired prefix.
307      */
308     public void setDefaultRole(String defaultRole) {
309         Assert.notNull(defaultRole, "The defaultRole property cannot be set to null");
310         this.defaultRole = new GrantedAuthorityImpl(defaultRole);
311     }
312 
313     public void setGroupRoleAttribute(String groupRoleAttribute) {
314         Assert.notNull(groupRoleAttribute, "groupRoleAttribute must not be null");
315         this.groupRoleAttribute = groupRoleAttribute;
316     }
317 
318     public void setGroupSearchFilter(String groupSearchFilter) {
319         Assert.notNull(groupSearchFilter, "groupSearchFilter must not be null");
320         this.groupSearchFilter = groupSearchFilter;
321     }
322 
323     public void setRolePrefix(String rolePrefix) {
324         Assert.notNull(rolePrefix, "rolePrefix must not be null");
325         this.rolePrefix = rolePrefix;
326     }
327 
328     /**
329      * If set to true, a subtree scope search will be performed. If false a single-level search is used.
330      *
331      * @param searchSubtree set to true to enable searching of the entire tree below the <tt>groupSearchBase</tt>.
332      */
333     public void setSearchSubtree(boolean searchSubtree) {
334         int searchScope = searchSubtree ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE;
335         searchControls.setSearchScope(searchScope);
336     }
337 }