| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
| DefaultLdapAuthoritiesPopulator |
|
| 1.7333333333333334;1.733 |
| 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 | 2 | 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 | 5 | private GrantedAuthority defaultRole = null; |
| 101 | ||
| 102 | /** |
|
| 103 | * An initial context factory is only required if searching for groups is required. |
|
| 104 | */ |
|
| 105 | 5 | 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 | 5 | private SearchControls searchControls = new SearchControls(); |
| 113 | ||
| 114 | /** |
|
| 115 | * The ID of the attribute which contains the role name for a group |
|
| 116 | */ |
|
| 117 | 5 | private String groupRoleAttribute = "cn"; |
| 118 | ||
| 119 | /** |
|
| 120 | * The base DN from which the search for group membership should be performed |
|
| 121 | */ |
|
| 122 | 5 | private String groupSearchBase = null; |
| 123 | ||
| 124 | /** |
|
| 125 | * The pattern to be used for the user search. {0} is the user's DN |
|
| 126 | */ |
|
| 127 | 5 | 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 | 5 | private String rolePrefix = "ROLE_"; |
| 135 | 5 | 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 | 5 | public DefaultLdapAuthoritiesPopulator(InitialDirContextFactory initialDirContextFactory, String groupSearchBase) { |
| 148 | 5 | this.setInitialDirContextFactory(initialDirContextFactory); |
| 149 | 5 | this.setGroupSearchBase(groupSearchBase); |
| 150 | 5 | } |
| 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 | 5 | 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 | 5 | String userDn = userDetails.getDn(); |
| 176 | ||
| 177 | 5 | if (logger.isDebugEnabled()) { |
| 178 | 0 | logger.debug("Getting authorities for user " + userDn); |
| 179 | } |
|
| 180 | ||
| 181 | 5 | Set roles = getGroupMembershipRoles(userDn, userDetails.getUsername()); |
| 182 | ||
| 183 | // Temporary use of deprecated method |
|
| 184 | 5 | Set oldGroupRoles = getGroupMembershipRoles(userDn, userDetails.getAttributes()); |
| 185 | ||
| 186 | 5 | if (oldGroupRoles != null) { |
| 187 | 5 | roles.addAll(oldGroupRoles); |
| 188 | } |
|
| 189 | ||
| 190 | 5 | Set extraRoles = getAdditionalRoles(userDetails); |
| 191 | ||
| 192 | 5 | if (extraRoles != null) { |
| 193 | 0 | roles.addAll(extraRoles); |
| 194 | } |
|
| 195 | ||
| 196 | 5 | if (defaultRole != null) { |
| 197 | 1 | roles.add(defaultRole); |
| 198 | } |
|
| 199 | ||
| 200 | 5 | 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 | 5 | Set authorities = new HashSet(); |
| 218 | ||
| 219 | 5 | if (getGroupSearchBase() == null) { |
| 220 | 0 | return authorities; |
| 221 | } |
|
| 222 | ||
| 223 | 5 | if (logger.isDebugEnabled()) { |
| 224 | 0 | logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter " |
| 225 | + groupSearchFilter + " in search base '" + getGroupSearchBase() + "'"); |
|
| 226 | } |
|
| 227 | ||
| 228 | 5 | Set userRoles = ldapTemplate.searchForSingleAttributeValues(getGroupSearchBase(), groupSearchFilter, |
| 229 | new String[]{userDn, username}, groupRoleAttribute); |
|
| 230 | ||
| 231 | 5 | if (logger.isDebugEnabled()) { |
| 232 | 0 | logger.debug("Roles from search: " + userRoles); |
| 233 | } |
|
| 234 | ||
| 235 | 5 | Iterator it = userRoles.iterator(); |
| 236 | ||
| 237 | 13 | while (it.hasNext()) { |
| 238 | 8 | String role = (String) it.next(); |
| 239 | ||
| 240 | 8 | if (convertToUpperCase) { |
| 241 | 8 | role = role.toUpperCase(); |
| 242 | } |
|
| 243 | ||
| 244 | 8 | authorities.add(new GrantedAuthorityImpl(rolePrefix + role)); |
| 245 | 8 | } |
| 246 | ||
| 247 | 5 | 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 | 5 | return new HashSet(); |
| 261 | } |
|
| 262 | ||
| 263 | protected InitialDirContextFactory getInitialDirContextFactory() { |
|
| 264 | 0 | 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 | 5 | Assert.notNull(initialDirContextFactory, "InitialDirContextFactory must not be null"); |
| 274 | 5 | this.initialDirContextFactory = initialDirContextFactory; |
| 275 | ||
| 276 | 5 | ldapTemplate = new LdapTemplate(initialDirContextFactory); |
| 277 | 5 | ldapTemplate.setSearchControls(searchControls); |
| 278 | 5 | } |
| 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 | 5 | Assert.notNull(groupSearchBase, "The groupSearchBase (name to search under), must not be null."); |
| 288 | 5 | this.groupSearchBase = groupSearchBase; |
| 289 | 5 | if (groupSearchBase.length() == 0) { |
| 290 | 0 | logger.info("groupSearchBase is empty. Searches will be performed from the root: " |
| 291 | + getInitialDirContextFactory().getRootDn()); |
|
| 292 | } |
|
| 293 | 5 | } |
| 294 | ||
| 295 | protected String getGroupSearchBase() { |
|
| 296 | 10 | return groupSearchBase; |
| 297 | } |
|
| 298 | ||
| 299 | public void setConvertToUpperCase(boolean convertToUpperCase) { |
|
| 300 | 4 | this.convertToUpperCase = convertToUpperCase; |
| 301 | 4 | } |
| 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 | 1 | Assert.notNull(defaultRole, "The defaultRole property cannot be set to null"); |
| 310 | 1 | this.defaultRole = new GrantedAuthorityImpl(defaultRole); |
| 311 | 1 | } |
| 312 | ||
| 313 | public void setGroupRoleAttribute(String groupRoleAttribute) { |
|
| 314 | 4 | Assert.notNull(groupRoleAttribute, "groupRoleAttribute must not be null"); |
| 315 | 4 | this.groupRoleAttribute = groupRoleAttribute; |
| 316 | 4 | } |
| 317 | ||
| 318 | public void setGroupSearchFilter(String groupSearchFilter) { |
|
| 319 | 2 | Assert.notNull(groupSearchFilter, "groupSearchFilter must not be null"); |
| 320 | 2 | this.groupSearchFilter = groupSearchFilter; |
| 321 | 2 | } |
| 322 | ||
| 323 | public void setRolePrefix(String rolePrefix) { |
|
| 324 | 1 | Assert.notNull(rolePrefix, "rolePrefix must not be null"); |
| 325 | 1 | this.rolePrefix = rolePrefix; |
| 326 | 1 | } |
| 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 | 3 | int searchScope = searchSubtree ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE; |
| 335 | 3 | searchControls.setSearchScope(searchScope); |
| 336 | 3 | } |
| 337 | } |