001: package ru.emdev.EmForge.security.ldap;
002:
003: import java.util.ArrayList;
004: import java.util.Collection;
005: import java.util.List;
006:
007: import javax.naming.NamingEnumeration;
008: import javax.naming.NamingException;
009: import javax.naming.directory.Attribute;
010: import javax.naming.directory.Attributes;
011: import javax.naming.directory.DirContext;
012: import javax.naming.directory.SearchControls;
013: import javax.naming.directory.SearchResult;
014:
015: import org.acegisecurity.ldap.InitialDirContextFactory;
016: import org.acegisecurity.ldap.LdapCallback;
017: import org.acegisecurity.ldap.LdapDataAccessException;
018: import org.acegisecurity.ldap.LdapTemplate;
019: import org.acegisecurity.ldap.LdapUserSearch;
020: import org.acegisecurity.ldap.search.FilterBasedLdapUserSearch;
021: import org.acegisecurity.userdetails.UserDetails;
022: import org.acegisecurity.userdetails.UserDetailsService;
023: import org.acegisecurity.userdetails.UsernameNotFoundException;
024: import org.acegisecurity.userdetails.ldap.LdapUserDetails;
025: import org.apache.commons.logging.Log;
026: import org.apache.commons.logging.LogFactory;
027: import org.springframework.beans.factory.InitializingBean;
028: import org.springframework.dao.DataAccessException;
029: import org.springframework.dao.IncorrectResultSizeDataAccessException;
030:
031: import ru.emdev.EmForge.security.EmForgeUserDetailsService;
032:
033: public class LdapUserDetailsServiceImpl implements
034: EmForgeUserDetailsService, LdapUserSearch, InitializingBean {
035:
036: public UserDetails loadUserByUsername(String username)
037: throws UsernameNotFoundException, DataAccessException {
038: return searchForUser(username);
039: }
040:
041: //~ Static fields/initializers =====================================================================================
042:
043: private static final Log logger = LogFactory
044: .getLog(FilterBasedLdapUserSearch.class);
045:
046: //~ Instance fields ================================================================================================
047:
048: private InitialDirContextFactory initialDirContextFactory;
049: protected EmForgeUserDetailsMapper userDetailsMapper;
050:
051: /**
052: * The LDAP SearchControls object used for the search. Shared between searches so shouldn't be modified
053: * once the bean has been configured.
054: */
055: private SearchControls searchControls = new SearchControls();
056:
057: /** Context name to search in, relative to the root DN of the configured InitialDirContextFactory. */
058: private String searchBase = "";
059:
060: /**
061: * The filter expression used in the user search. This is an LDAP search filter (as defined in 'RFC 2254')
062: * with optional arguments. See the documentation for the <tt>search</tt> methods in {@link
063: * javax.naming.directory.DirContext DirContext} for more information.<p>In this case, the username is the
064: * only parameter.</p>
065: * Possible examples are:
066: * <ul>
067: * <li>(uid={0}) - this would search for a username match on the uid attribute.</li>
068: * </ul>
069: * TODO: more examples.
070: */
071: private String searchFilter;
072:
073: /** Filter used for getting list of users */
074: private String listFilter;
075: private String listDNFilter;
076: private String listDNAttrName = "member";
077:
078: public void setSearchBase(String i_searchBase) {
079: searchBase = i_searchBase;
080: }
081:
082: public void setSearchFilter(String i_searchFilter) {
083: searchFilter = i_searchFilter;
084: }
085:
086: public void setListFilter(String i_listFilter) {
087: listFilter = i_listFilter;
088: }
089:
090: public void setListDNFilter(String i_listDNFilter) {
091: listDNFilter = i_listDNFilter;
092: }
093:
094: public void setListDNAttrName(String i_listDNAttrName) {
095: listDNAttrName = i_listDNAttrName;
096: }
097:
098: public void setInitialDirContextFactory(
099: InitialDirContextFactory i_initialDirContextFactory) {
100: initialDirContextFactory = i_initialDirContextFactory;
101: }
102:
103: public void setUserDetailsMapper(
104: EmForgeUserDetailsMapper i_userDetailsMapper) {
105: userDetailsMapper = i_userDetailsMapper;
106: }
107:
108: public void afterPropertiesSet() throws Exception {
109: if (initialDirContextFactory == null) {
110: throw new IllegalArgumentException(
111: "initialDirContextFactory must not be null");
112: }
113:
114: if (searchFilter == null) {
115: throw new IllegalArgumentException(
116: "searchFilter must not be null");
117: }
118: if (searchBase == null) {
119: throw new IllegalArgumentException(
120: "searchBase must not be null (an empty string is acceptable)");
121: }
122:
123: if (userDetailsMapper == null) {
124: // created defaulr
125: userDetailsMapper = new EmForgeUserDetailsMapper();
126: }
127: }
128:
129: //~ Methods ========================================================================================================
130:
131: /**
132: * Return the LdapUserDetails containing the user's information
133: *
134: * @param username the username to search for.
135: *
136: * @return An LdapUserDetails object containing the details of the located user's directory entry
137: *
138: * @throws UsernameNotFoundException if no matching entry is found.
139: */
140: public LdapUserDetails searchForUser(String username) {
141: DirContext ctx = initialDirContextFactory
142: .newInitialDirContext();
143:
144: if (logger.isDebugEnabled()) {
145: logger.debug("Searching for user '" + username
146: + "', in context " + ctx + ", with user search "
147: + this .toString());
148: }
149:
150: LdapTemplate template = new LdapTemplate(
151: initialDirContextFactory);
152:
153: template.setSearchControls(searchControls);
154:
155: try {
156: EmForgeUserLdap.Essence user = null;
157:
158: try {
159: user = (EmForgeUserLdap.Essence) template
160: .searchForSingleEntry(searchBase, searchFilter,
161: new String[] { username },
162: userDetailsMapper);
163: } catch (IncorrectResultSizeDataAccessException notFound) {
164: // maybe username is full DN?
165:
166: // problem - the specified name probably is full DN
167: user = (EmForgeUserLdap.Essence) template
168: .retrieveEntry(username, userDetailsMapper,
169: null);
170: }
171: user.setUserDetailsService(this );
172:
173: return user.createUserDetails();
174: } catch (IncorrectResultSizeDataAccessException notFound) {
175: if (notFound.getActualSize() == 0) {
176: throw new UsernameNotFoundException("User " + username
177: + " not found in directory.");
178: }
179: // Search should never return multiple results if properly configured, so just rethrow
180: throw notFound;
181: } catch (LdapDataAccessException dataAccessEx) {
182: throw new UsernameNotFoundException("User " + username
183: + " not found in directory.");
184: }
185: }
186:
187: /**
188: * Sets the corresponding property on the {@link SearchControls} instance used in the search.
189: *
190: * @param deref the derefLinkFlag value as defined in SearchControls..
191: */
192: public void setDerefLinkFlag(boolean deref) {
193: searchControls.setDerefLinkFlag(deref);
194: }
195:
196: /**
197: * If true then searches the entire subtree as identified by context, if false (the default) then only
198: * searches the level identified by the context.
199: *
200: * @param searchSubtree true the underlying search controls should be set to SearchControls.SUBTREE_SCOPE
201: * rather than SearchControls.ONELEVEL_SCOPE.
202: */
203: public void setSearchSubtree(boolean searchSubtree) {
204: searchControls
205: .setSearchScope(searchSubtree ? SearchControls.SUBTREE_SCOPE
206: : SearchControls.ONELEVEL_SCOPE);
207: }
208:
209: /**
210: * The time to wait before the search fails; the default is zero, meaning forever.
211: *
212: * @param searchTimeLimit the time limit for the search (in milliseconds).
213: */
214: public void setSearchTimeLimit(int searchTimeLimit) {
215: searchControls.setTimeLimit(searchTimeLimit);
216: }
217:
218: public String toString() {
219: StringBuffer sb = new StringBuffer();
220:
221: sb.append("[ searchFilter: '").append(searchFilter).append(
222: "', ");
223: sb.append("searchBase: '").append(searchBase).append("'");
224: sb
225: .append(", scope: ")
226: .append(
227: (searchControls.getSearchScope() == SearchControls.SUBTREE_SCOPE) ? "subtree"
228: : "single-level, ");
229: sb.append("searchTimeLimit: ").append(
230: searchControls.getTimeLimit());
231: sb.append("derefLinkFlag: ").append(
232: searchControls.getDerefLinkFlag()).append(" ]");
233:
234: return sb.toString();
235: }
236:
237: /** Gets list of users
238: * We can get list of users by two ways:
239: * First- enumerates all users with specified attribute search (listFilter) - I like this way - but - in our case for some users I did not get all users
240: * Second - to enumerate users DN in the group entry
241: *
242: */
243: public Collection<UserDetails> getAllUsers() {
244: if (listFilter != null) {
245: return getUsersByMemberOf();
246: } else {
247: return getUsersFromGroup();
248: }
249: }
250:
251: @SuppressWarnings("unchecked")
252: private Collection<UserDetails> getUsersByMemberOf() {
253: LdapTemplate template = new LdapTemplate(
254: initialDirContextFactory);
255:
256: template.setSearchControls(searchControls);
257: final UserDetailsService userDetailsService = this ;
258:
259: Collection<UserDetails> users = null;
260:
261: users = (Collection<UserDetails>) template
262: .execute(new LdapCallback() {
263: public Object doInDirContext(DirContext dirContext)
264: throws NamingException {
265: NamingEnumeration results = null;
266: List<UserDetails> users = new ArrayList<UserDetails>();
267:
268: results = dirContext.search(searchBase,
269: listFilter, searchControls);
270: while (results.hasMore()) {
271:
272: SearchResult searchResult = (SearchResult) results
273: .next();
274: Attributes attributes = searchResult
275: .getAttributes();
276:
277: String dn = attributes.get(
278: "distinguishedName").toString();
279: EmForgeUserLdap.Essence essence = (EmForgeUserLdap.Essence) userDetailsMapper
280: .mapAttributes(dn, attributes);
281: essence
282: .setUserDetailsService(userDetailsService);
283:
284: users.add(essence.createUserDetails());
285: }
286:
287: return users;
288: }
289: });
290:
291: return users;
292: }
293:
294: @SuppressWarnings("unchecked")
295: private Collection<UserDetails> getUsersFromGroup() {
296: LdapTemplate template = new LdapTemplate(
297: initialDirContextFactory);
298:
299: template.setSearchControls(searchControls);
300: Collection<UserDetails> users = null;
301:
302: users = (Collection<UserDetails>) template
303: .execute(new LdapCallback() {
304: public Object doInDirContext(DirContext dirContext)
305: throws NamingException {
306: List<UserDetails> users = new ArrayList<UserDetails>();
307: Attributes attributes = dirContext
308: .getAttributes(listDNFilter);
309:
310: Attribute members = attributes
311: .get(listDNAttrName);
312:
313: for (int i = 0; i < members.size(); i++) {
314: Object member = members.get(i);
315: String userDN = member.toString();
316:
317: users.add(loadUserByUsername(userDN));
318: }
319:
320: return users;
321: }
322: });
323:
324: return users;
325: }
326:
327: }
|