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.ldap.search;
17  
18  import org.acegisecurity.ldap.InitialDirContextFactory;
19  import org.acegisecurity.ldap.LdapTemplate;
20  import org.acegisecurity.ldap.LdapUserSearch;
21  
22  import org.acegisecurity.userdetails.UsernameNotFoundException;
23  import org.acegisecurity.userdetails.ldap.LdapUserDetails;
24  import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;
25  import org.acegisecurity.userdetails.ldap.LdapUserDetailsMapper;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  import org.springframework.dao.IncorrectResultSizeDataAccessException;
31  
32  import org.springframework.util.Assert;
33  
34  import javax.naming.directory.DirContext;
35  import javax.naming.directory.SearchControls;
36  
37  
38  /**
39   * LdapUserSearch implementation which uses an Ldap filter to locate the user.
40   *
41   * @author Robert Sanders
42   * @author Luke Taylor
43   * @version $Id: FilterBasedLdapUserSearch.java 2626 2008-02-14 18:58:08Z luke_t $
44   *
45   * @see SearchControls
46   */
47  public class FilterBasedLdapUserSearch implements LdapUserSearch {
48      //~ Static fields/initializers =====================================================================================
49  
50      private static final Log logger = LogFactory.getLog(FilterBasedLdapUserSearch.class);
51  
52      //~ Instance fields ================================================================================================
53  
54      private InitialDirContextFactory initialDirContextFactory;
55      private LdapUserDetailsMapper userDetailsMapper = new LdapUserDetailsMapper();
56  
57      /**
58       * The LDAP SearchControls object used for the search. Shared between searches so shouldn't be modified
59       * once the bean has been configured.
60       */
61      private SearchControls searchControls = new SearchControls();
62  
63      /** Context name to search in, relative to the root DN of the configured InitialDirContextFactory. */
64      private String searchBase = "";
65  
66      /**
67       * The filter expression used in the user search. This is an LDAP search filter (as defined in 'RFC 2254')
68       * with optional arguments. See the documentation for the <tt>search</tt> methods in {@link
69       * javax.naming.directory.DirContext DirContext} for more information.<p>In this case, the username is the
70       * only parameter.</p>
71       *  Possible examples are:
72       *  <ul>
73       *      <li>(uid={0}) - this would search for a username match on the uid attribute.</li>
74       *  </ul>
75       *  TODO: more examples.
76       */
77      private String searchFilter;
78  
79      //~ Constructors ===================================================================================================
80  
81      public FilterBasedLdapUserSearch(String searchBase, String searchFilter,
82              InitialDirContextFactory initialDirContextFactory) {
83          Assert.notNull(initialDirContextFactory, "initialDirContextFactory must not be null");
84          Assert.notNull(searchFilter, "searchFilter must not be null.");
85          Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable).");
86  
87          this.searchFilter = searchFilter;
88          this.initialDirContextFactory = initialDirContextFactory;
89          this.searchBase = searchBase;
90  
91          if (searchBase.length() == 0) {
92              logger.info("SearchBase not set. Searches will be performed from the root: "
93                  + initialDirContextFactory.getRootDn());
94          }
95      }
96  
97      //~ Methods ========================================================================================================
98  
99      /**
100      * Return the LdapUserDetails containing the user's information
101      *
102      * @param username the username to search for.
103      *
104      * @return An LdapUserDetails object containing the details of the located user's directory entry
105      *
106      * @throws UsernameNotFoundException if no matching entry is found.
107      */
108     public LdapUserDetails searchForUser(String username) {
109         if (logger.isDebugEnabled()) {
110             logger.debug("Searching for user '" + username + "', with user search "
111                 + this.toString());
112         }
113 
114         LdapTemplate template = new LdapTemplate(initialDirContextFactory);
115 
116         template.setSearchControls(searchControls);
117 
118         try {
119             LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) template.searchForSingleEntry(searchBase,
120                     searchFilter, new String[] {username}, userDetailsMapper);
121             user.setUsername(username);
122 
123             return user.createUserDetails();
124         } catch (IncorrectResultSizeDataAccessException notFound) {
125             if (notFound.getActualSize() == 0) {
126                 throw new UsernameNotFoundException("User " + username + " not found in directory.");
127             }
128             // Search should never return multiple results if properly configured, so just rethrow
129             throw notFound;
130         }
131     }
132 
133     /**
134      * Sets the corresponding property on the {@link SearchControls} instance used in the search.
135      *
136      * @param deref the derefLinkFlag value as defined in SearchControls..
137      */
138     public void setDerefLinkFlag(boolean deref) {
139         searchControls.setDerefLinkFlag(deref);
140     }
141 
142     /**
143      * If true then searches the entire subtree as identified by context, if false (the default) then only
144      * searches the level identified by the context.
145      *
146      * @param searchSubtree true the underlying search controls should be set to SearchControls.SUBTREE_SCOPE
147      * rather than SearchControls.ONELEVEL_SCOPE.
148      */
149     public void setSearchSubtree(boolean searchSubtree) {
150         searchControls.setSearchScope(searchSubtree ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE);
151     }
152 
153     /**
154      * The time to wait before the search fails; the default is zero, meaning forever.
155      *
156      * @param searchTimeLimit the time limit for the search (in milliseconds).
157      */
158     public void setSearchTimeLimit(int searchTimeLimit) {
159         searchControls.setTimeLimit(searchTimeLimit);
160     }
161 
162     protected LdapUserDetailsMapper getUserDetailsMapper() {
163         return userDetailsMapper;
164     }
165 
166     public void setUserDetailsMapper(LdapUserDetailsMapper userDetailsMapper) {
167         this.userDetailsMapper = userDetailsMapper;
168     }
169 
170     public String toString() {
171         StringBuffer sb = new StringBuffer();
172 
173         sb.append("[ searchFilter: '").append(searchFilter).append("', ");
174         sb.append("searchBase: '").append(searchBase).append("'");
175         sb.append(", scope: ")
176           .append(searchControls.getSearchScope() == SearchControls.SUBTREE_SCOPE ? "subtree" : "single-level, ");
177         sb.append("searchTimeLimit: ").append(searchControls.getTimeLimit());
178         sb.append("derefLinkFlag: ").append(searchControls.getDerefLinkFlag()).append(" ]");
179 
180         return sb.toString();
181     }
182 }