001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.ldap.search;
017:
018: import org.acegisecurity.ldap.InitialDirContextFactory;
019: import org.acegisecurity.ldap.LdapTemplate;
020: import org.acegisecurity.ldap.LdapUserSearch;
021:
022: import org.acegisecurity.userdetails.UsernameNotFoundException;
023: import org.acegisecurity.userdetails.ldap.LdapUserDetails;
024: import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;
025: import org.acegisecurity.userdetails.ldap.LdapUserDetailsMapper;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: import org.springframework.dao.IncorrectResultSizeDataAccessException;
031:
032: import org.springframework.util.Assert;
033:
034: import javax.naming.directory.DirContext;
035: import javax.naming.directory.SearchControls;
036:
037: /**
038: * LdapUserSearch implementation which uses an Ldap filter to locate the user.
039: *
040: * @author Robert Sanders
041: * @author Luke Taylor
042: * @version $Id: FilterBasedLdapUserSearch.java 1928 2007-07-24 17:01:36Z luke_t $
043: *
044: * @see SearchControls
045: */
046: public class FilterBasedLdapUserSearch implements LdapUserSearch {
047: //~ Static fields/initializers =====================================================================================
048:
049: private static final Log logger = LogFactory
050: .getLog(FilterBasedLdapUserSearch.class);
051:
052: //~ Instance fields ================================================================================================
053:
054: private InitialDirContextFactory initialDirContextFactory;
055: private LdapUserDetailsMapper userDetailsMapper = new LdapUserDetailsMapper();
056:
057: /**
058: * The LDAP SearchControls object used for the search. Shared between searches so shouldn't be modified
059: * once the bean has been configured.
060: */
061: private SearchControls searchControls = new SearchControls();
062:
063: /** Context name to search in, relative to the root DN of the configured InitialDirContextFactory. */
064: private String searchBase = "";
065:
066: /**
067: * The filter expression used in the user search. This is an LDAP search filter (as defined in 'RFC 2254')
068: * with optional arguments. See the documentation for the <tt>search</tt> methods in {@link
069: * javax.naming.directory.DirContext DirContext} for more information.<p>In this case, the username is the
070: * only parameter.</p>
071: * Possible examples are:
072: * <ul>
073: * <li>(uid={0}) - this would search for a username match on the uid attribute.</li>
074: * </ul>
075: * TODO: more examples.
076: */
077: private String searchFilter;
078:
079: //~ Constructors ===================================================================================================
080:
081: public FilterBasedLdapUserSearch(String searchBase,
082: String searchFilter,
083: InitialDirContextFactory initialDirContextFactory) {
084: Assert.notNull(initialDirContextFactory,
085: "initialDirContextFactory must not be null");
086: Assert.notNull(searchFilter, "searchFilter must not be null.");
087: Assert
088: .notNull(searchBase,
089: "searchBase must not be null (an empty string is acceptable).");
090:
091: this .searchFilter = searchFilter;
092: this .initialDirContextFactory = initialDirContextFactory;
093: this .searchBase = searchBase;
094:
095: if (searchBase.length() == 0) {
096: logger
097: .info("SearchBase not set. Searches will be performed from the root: "
098: + initialDirContextFactory.getRootDn());
099: }
100: }
101:
102: //~ Methods ========================================================================================================
103:
104: /**
105: * Return the LdapUserDetails containing the user's information
106: *
107: * @param username the username to search for.
108: *
109: * @return An LdapUserDetails object containing the details of the located user's directory entry
110: *
111: * @throws UsernameNotFoundException if no matching entry is found.
112: */
113: public LdapUserDetails searchForUser(String username) {
114: if (logger.isDebugEnabled()) {
115: logger.debug("Searching for user '" + username
116: + "', with user search " + this .toString());
117: }
118:
119: LdapTemplate template = new LdapTemplate(
120: initialDirContextFactory);
121:
122: template.setSearchControls(searchControls);
123:
124: try {
125: LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) template
126: .searchForSingleEntry(searchBase, searchFilter,
127: new String[] { username },
128: userDetailsMapper);
129: user.setUsername(username);
130:
131: return user.createUserDetails();
132: } catch (IncorrectResultSizeDataAccessException notFound) {
133: if (notFound.getActualSize() == 0) {
134: throw new UsernameNotFoundException("User " + username
135: + " not found in directory.");
136: }
137: // Search should never return multiple results if properly configured, so just rethrow
138: throw notFound;
139: }
140: }
141:
142: /**
143: * Sets the corresponding property on the {@link SearchControls} instance used in the search.
144: *
145: * @param deref the derefLinkFlag value as defined in SearchControls..
146: */
147: public void setDerefLinkFlag(boolean deref) {
148: searchControls.setDerefLinkFlag(deref);
149: }
150:
151: /**
152: * If true then searches the entire subtree as identified by context, if false (the default) then only
153: * searches the level identified by the context.
154: *
155: * @param searchSubtree true the underlying search controls should be set to SearchControls.SUBTREE_SCOPE
156: * rather than SearchControls.ONELEVEL_SCOPE.
157: */
158: public void setSearchSubtree(boolean searchSubtree) {
159: searchControls
160: .setSearchScope(searchSubtree ? SearchControls.SUBTREE_SCOPE
161: : SearchControls.ONELEVEL_SCOPE);
162: }
163:
164: /**
165: * The time to wait before the search fails; the default is zero, meaning forever.
166: *
167: * @param searchTimeLimit the time limit for the search (in milliseconds).
168: */
169: public void setSearchTimeLimit(int searchTimeLimit) {
170: searchControls.setTimeLimit(searchTimeLimit);
171: }
172:
173: public String toString() {
174: StringBuffer sb = new StringBuffer();
175:
176: sb.append("[ searchFilter: '").append(searchFilter).append(
177: "', ");
178: sb.append("searchBase: '").append(searchBase).append("'");
179: sb
180: .append(", scope: ")
181: .append(
182: searchControls.getSearchScope() == SearchControls.SUBTREE_SCOPE ? "subtree"
183: : "single-level, ");
184: sb.append("searchTimeLimit: ").append(
185: searchControls.getTimeLimit());
186: sb.append("derefLinkFlag: ").append(
187: searchControls.getDerefLinkFlag()).append(" ]");
188:
189: return sb.toString();
190: }
191: }
|