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 * <bean id="ldapAuthoritiesPopulator"
70 * class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
71 * <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
72 * <constructor-arg><value>ou=groups</value></constructor-arg>
73 * <property name="groupRoleAttribute"><value>ou</value></property>
74 * <!-- the following properties are shown with their default values -->
75 * <property name="searchSubTree"><value>false</value></property>
76 * <property name="rolePrefix"><value>ROLE_</value></property>
77 * <property name="convertToUpperCase"><value>true</value></property>
78 * </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 }