Coverage Report - org.acegisecurity.ldap.LdapTemplate
 
Classes in this File Line Coverage Branch Coverage Complexity
LdapTemplate
100% 
100% 
2
 
 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.ldap;
 17  
 
 18  
 import org.springframework.dao.DataAccessException;
 19  
 import org.springframework.dao.IncorrectResultSizeDataAccessException;
 20  
 
 21  
 import org.springframework.util.Assert;
 22  
 import org.springframework.util.StringUtils;
 23  
 
 24  
 import java.util.HashSet;
 25  
 import java.util.Set;
 26  
 
 27  
 import javax.naming.NameNotFoundException;
 28  
 import javax.naming.NamingEnumeration;
 29  
 import javax.naming.NamingException;
 30  
 import javax.naming.Context;
 31  
 import javax.naming.directory.Attribute;
 32  
 import javax.naming.directory.Attributes;
 33  
 import javax.naming.directory.DirContext;
 34  
 import javax.naming.directory.SearchControls;
 35  
 import javax.naming.directory.SearchResult;
 36  
 
 37  
 
 38  
 /**
 39  
  * LDAP equivalent of the Spring JdbcTemplate class.<p>This is mainly intended to simplify Ldap access within Acegi
 40  
  * Security's LDAP-related services.</p>
 41  
  *
 42  
  * @author Ben Alex
 43  
  * @author Luke Taylor
 44  
  */
 45  23
 public class LdapTemplate {
 46  
     //~ Static fields/initializers =====================================================================================
 47  
 
 48  1
     public static final String[] NO_ATTRS = new String[0];
 49  
 
 50  
     //~ Instance fields ================================================================================================
 51  
 
 52  
     private InitialDirContextFactory dirContextFactory;
 53  33
     private NamingExceptionTranslator exceptionTranslator = new LdapExceptionTranslator();
 54  
 
 55  
     /** Default search controls */
 56  33
     private SearchControls searchControls = new SearchControls();
 57  33
     private String password = null;
 58  33
     private String principalDn = null;
 59  
 
 60  
     //~ Constructors ===================================================================================================
 61  
 
 62  33
     public LdapTemplate(InitialDirContextFactory dirContextFactory) {
 63  33
         Assert.notNull(dirContextFactory, "An InitialDirContextFactory is required");
 64  33
         this.dirContextFactory = dirContextFactory;
 65  
 
 66  33
         searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
 67  33
     }
 68  
 
 69  
 /**
 70  
      *
 71  
      * @param dirContextFactory the source of DirContexts
 72  
      * @param userDn the user name to authenticate as when obtaining new contexts
 73  
      * @param password the user's password
 74  
      */
 75  
     public LdapTemplate(InitialDirContextFactory dirContextFactory, String userDn, String password) {
 76  5
         this(dirContextFactory);
 77  
 
 78  5
         Assert.hasLength(userDn, "userDn must not be null or empty");
 79  5
         Assert.notNull(password, "password cannot be null");
 80  
 
 81  5
         this.principalDn = userDn;
 82  5
         this.password = password;
 83  5
     }
 84  
 
 85  
     //~ Methods ========================================================================================================
 86  
 
 87  
     /**
 88  
      * Performs an LDAP compare operation of the value of an attribute for a particular directory entry.
 89  
      *
 90  
      * @param dn the entry who's attribute is to be used
 91  
      * @param attributeName the attribute who's value we want to compare
 92  
      * @param value the value to be checked against the directory value
 93  
      *
 94  
      * @return true if the supplied value matches that in the directory
 95  
      */
 96  
     public boolean compare(final String dn, final String attributeName, final Object value) {
 97  5
         final String comparisonFilter = "(" + attributeName + "={0})";
 98  
 
 99  5
         class LdapCompareCallback implements LdapCallback {
 100  
             public Object doInDirContext(DirContext ctx)
 101  
                 throws NamingException {
 102  5
                 SearchControls ctls = new SearchControls();
 103  5
                 ctls.setReturningAttributes(NO_ATTRS);
 104  5
                 ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
 105  
 
 106  5
                 String relativeName = LdapUtils.getRelativeName(dn, ctx);
 107  
 
 108  5
                 NamingEnumeration results = ctx.search(relativeName, comparisonFilter, new Object[] {value}, ctls);
 109  
 
 110  5
                 return Boolean.valueOf(results.hasMore());
 111  
             }
 112  
         }
 113  
 
 114  5
         Boolean matches = (Boolean) execute(new LdapCompareCallback());
 115  
 
 116  5
         return matches.booleanValue();
 117  
     }
 118  
 
 119  
     public Object execute(LdapCallback callback) throws DataAccessException {
 120  41
         DirContext ctx = null;
 121  
 
 122  
         try {
 123  41
             ctx = (principalDn == null) ? dirContextFactory.newInitialDirContext()
 124  
                                         : dirContextFactory.newInitialDirContext(principalDn, password);
 125  
 
 126  39
             return callback.doInDirContext(ctx);
 127  1
         } catch (NamingException exception) {
 128  1
             throw exceptionTranslator.translate("LdapCallback", exception);
 129  
         } finally {
 130  41
             LdapUtils.closeContext(ctx);
 131  
         }
 132  
     }
 133  
 
 134  
     public boolean nameExists(final String dn) {
 135  11
         Boolean exists = (Boolean) execute(new LdapCallback() {
 136  11
                 public Object doInDirContext(DirContext ctx)
 137  
                     throws NamingException {
 138  
                     try {
 139  11
                         Object obj = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
 140  9
                         if (obj instanceof Context) {
 141  8
                             LdapUtils.closeContext((Context) obj);
 142  
                         }
 143  
 
 144  2
                     } catch (NameNotFoundException nnfe) {
 145  2
                         return Boolean.FALSE;
 146  9
                     }
 147  
 
 148  9
                     return Boolean.TRUE;
 149  
                 }
 150  
             });
 151  
 
 152  11
         return exists.booleanValue();
 153  
     }
 154  
 
 155  
     /**
 156  
      * Composes an object from the attributes of the given DN.
 157  
      *
 158  
      * @param dn the directory entry which will be read
 159  
      * @param mapper maps the attributes to the required object
 160  
      * @param attributesToRetrieve the named attributes which will be retrieved from the directory entry.
 161  
      *
 162  
      * @return the object created by the mapper
 163  
      */
 164  
     public Object retrieveEntry(final String dn, final LdapEntryMapper mapper, final String[] attributesToRetrieve) {
 165  13
         return execute(new LdapCallback() {
 166  13
                 public Object doInDirContext(DirContext ctx)
 167  
                     throws NamingException {
 168  11
                     return mapper.mapAttributes(dn,
 169  
                         ctx.getAttributes(LdapUtils.getRelativeName(dn, ctx), attributesToRetrieve));
 170  
                 }
 171  
             });
 172  
     }
 173  
 
 174  
     /**
 175  
      * Performs a search using the supplied filter and returns the union of the values of the named attribute
 176  
      * found in all entries matched by the search. Note that one directory entry may have several values for the
 177  
      * attribute. Intended for role searches and similar scenarios.
 178  
      *
 179  
      * @param base the DN to search in
 180  
      * @param filter search filter to use
 181  
      * @param params the parameters to substitute in the search filter
 182  
      * @param attributeName the attribute who's values are to be retrieved.
 183  
      *
 184  
      * @return the set of String values for the attribute as a union of the values found in all the matching entries.
 185  
      */
 186  
     public Set searchForSingleAttributeValues(final String base, final String filter, final Object[] params,
 187  
         final String attributeName) {
 188  6
         class SingleAttributeSearchCallback implements LdapCallback {
 189  
             public Object doInDirContext(DirContext ctx)
 190  
                 throws NamingException {
 191  6
                 Set unionOfValues = new HashSet();
 192  
 
 193  
                 // We're only interested in a single attribute for this method, so we make a copy of
 194  
                 // the search controls and override the returningAttributes property
 195  6
                 SearchControls ctls = new SearchControls();
 196  
 
 197  6
                 ctls.setSearchScope(searchControls.getSearchScope());
 198  6
                 ctls.setTimeLimit(searchControls.getTimeLimit());
 199  6
                 ctls.setDerefLinkFlag(searchControls.getDerefLinkFlag());
 200  6
                 ctls.setReturningAttributes(new String[] {attributeName});
 201  
 
 202  6
                 NamingEnumeration matchingEntries = ctx.search(base, filter, params, ctls);
 203  
 
 204  17
                 while (matchingEntries.hasMore()) {
 205  11
                     SearchResult result = (SearchResult) matchingEntries.next();
 206  11
                     Attributes attrs = result.getAttributes();
 207  
 
 208  
                     // There should only be one attribute in each matching entry.
 209  11
                     NamingEnumeration returnedAttributes = attrs.getAll();
 210  
 
 211  22
                     while (returnedAttributes.hasMore()) {
 212  11
                         Attribute returnedAttribute = (Attribute) returnedAttributes.next();
 213  11
                         NamingEnumeration attributeValues = returnedAttribute.getAll();
 214  
 
 215  22
                         while (attributeValues.hasMore()) {
 216  11
                             Object value = attributeValues.next();
 217  
 
 218  11
                             unionOfValues.add(value.toString());
 219  11
                         }
 220  11
                     }
 221  11
                 }
 222  
 
 223  6
                 return unionOfValues;
 224  
             }
 225  
         }
 226  
 
 227  6
         return (Set) execute(new SingleAttributeSearchCallback());
 228  
     }
 229  
 
 230  
     /**
 231  
      * Performs a search, with the requirement that the search shall return a single directory entry, and uses
 232  
      * the supplied mapper to create the object from that entry.
 233  
      *
 234  
      * @param base
 235  
      * @param filter
 236  
      * @param params
 237  
      * @param mapper
 238  
      *
 239  
      * @return the object created by the mapper from the matching entry
 240  
      *
 241  
      * @throws IncorrectResultSizeDataAccessException if no results are found or the search returns more than one
 242  
      *         result.
 243  
      */
 244  
     public Object searchForSingleEntry(final String base, final String filter, final Object[] params,
 245  
         final LdapEntryMapper mapper) {
 246  5
         return execute(new LdapCallback() {
 247  5
                 public Object doInDirContext(DirContext ctx)
 248  
                     throws NamingException {
 249  5
                     NamingEnumeration results = ctx.search(base, filter, params, searchControls);
 250  
 
 251  5
                     if (!results.hasMore()) {
 252  1
                         throw new IncorrectResultSizeDataAccessException(1, 0);
 253  
                     }
 254  
 
 255  4
                     SearchResult searchResult = (SearchResult) results.next();
 256  
 
 257  4
                     if (results.hasMore()) {
 258  
                         // We don't know how many results but set to 2 which is good enough
 259  1
                         throw new IncorrectResultSizeDataAccessException(1, 2);
 260  
                     }
 261  
 
 262  
                     // Work out the DN of the matched entry
 263  3
                     StringBuffer dn = new StringBuffer(searchResult.getName());
 264  
 
 265  3
                     if (base.length() > 0) {
 266  2
                         dn.append(",");
 267  2
                         dn.append(base);
 268  
                     }
 269  
 
 270  3
                     String nameInNamespace = ctx.getNameInNamespace();
 271  
 
 272  3
                     if (StringUtils.hasLength(nameInNamespace)) {
 273  3
                         dn.append(",");
 274  3
                         dn.append(nameInNamespace);
 275  
                     }
 276  
 
 277  3
                     return mapper.mapAttributes(dn.toString(), searchResult.getAttributes());
 278  
                 }
 279  
             });
 280  
     }
 281  
 
 282  
     /**
 283  
      * Sets the search controls which will be used for search operations by the template.
 284  
      *
 285  
      * @param searchControls the SearchControls instance which will be cached in the template.
 286  
      */
 287  
     public void setSearchControls(SearchControls searchControls) {
 288  10
         this.searchControls = searchControls;
 289  10
     }
 290  
 
 291  
     //~ Inner Classes ==================================================================================================
 292  
 
 293  66
     private static class LdapExceptionTranslator implements NamingExceptionTranslator {
 294  
         public DataAccessException translate(String task, NamingException e) {
 295  1
             return new LdapDataAccessException(task + ";" + e.getMessage(), e);
 296  
         }
 297  
     }
 298  
 }