1 package com.atlassian.user.impl.ldap.search.query;
2
3 /**
4 * Note - any LDAP search query returned from this parserEntity must also be related, finally, to an appropriate
5 * base context in the LDAP DIT. This could be a context under which all group and user entries or stored
6 * or separate contexts for each entity type. The global context will be specified in the JNDI context which
7 * binds to the LDAP server.
8 */
9
10 import com.atlassian.user.EntityException;
11 import com.atlassian.user.repository.RepositoryIdentifier;
12 import com.atlassian.user.impl.RepositoryException;
13 import com.atlassian.user.impl.ldap.DefaultLDAPGroupFactory;
14 import com.atlassian.user.impl.ldap.LDAPGroupFactory;
15 import com.atlassian.user.impl.ldap.LDAPUserFactory;
16 import com.atlassian.user.impl.ldap.LiteralFilter;
17 import com.atlassian.user.impl.ldap.adaptor.LDAPGroupAdaptor;
18 import com.atlassian.user.impl.ldap.properties.LdapSearchProperties;
19 import com.atlassian.user.impl.ldap.properties.LdapMembershipProperties;
20 import com.atlassian.user.impl.ldap.repository.LdapContextFactory;
21 import com.atlassian.user.impl.ldap.search.DefaultLDAPUserAdaptor;
22 import com.atlassian.user.impl.ldap.search.LDAPPagerInfo;
23 import com.atlassian.user.impl.ldap.search.LDAPUserAdaptor;
24 import com.atlassian.user.impl.ldap.search.LdapFilterFactory;
25 import com.atlassian.user.impl.ldap.search.page.LDAPEntityPager;
26 import com.atlassian.user.search.DefaultSearchResult;
27 import com.atlassian.user.search.SearchResult;
28 import com.atlassian.user.search.page.Pager;
29 import com.atlassian.user.search.query.*;
30 import com.atlassian.util.profiling.UtilTimerStack;
31 import net.sf.ldaptemplate.support.LdapEncoder;
32
33 import javax.naming.NamingEnumeration;
34 import javax.naming.NamingException;
35 import javax.naming.directory.Attribute;
36 import javax.naming.directory.Attributes;
37 import java.util.Iterator;
38
39 public class LDAPEntityQueryParser implements EntityQueryParser
40 {
41 public static final String OPEN_PARAN = "(";
42 public static final String CLOSE_PARAN = ")";
43 public static final String EQ = "=";
44 public static final String AND = "&";
45 public static final String OR = "|";
46 public static final String WILDCARD = TermQuery.WILDCARD;
47
48 private final LdapContextFactory repository;
49 private final LDAPUserAdaptor userAdaptor;
50 private final LDAPGroupAdaptor groupAdaptor;
51 private final LDAPGroupFactory groupFactory;
52 private final LDAPUserFactory userFactory;
53
54 private final RepositoryIdentifier repositoryIdentifier;
55 private final LdapSearchProperties searchProperties;
56 private final LdapMembershipProperties membershipProperties;
57
58 public LDAPEntityQueryParser(LdapContextFactory repository, LDAPGroupAdaptor groupAdaptor,
59 RepositoryIdentifier repositoryIdentifier, LDAPUserFactory userFactory,
60 LdapSearchProperties searchProperties, LdapMembershipProperties membershipProperties,
61 LdapFilterFactory filterFactory)
62 {
63 this.repositoryIdentifier = repositoryIdentifier;
64 this.repository = repository;
65 this.groupAdaptor = groupAdaptor;
66 this.userFactory = userFactory;
67 this.userAdaptor = new DefaultLDAPUserAdaptor(this.repository, searchProperties, filterFactory);
68 this.groupFactory = new DefaultLDAPGroupFactory(searchProperties, membershipProperties);
69 this.searchProperties = searchProperties;
70 this.membershipProperties = membershipProperties;
71 }
72
73 public com.atlassian.user.search.SearchResult findUsers(Query query) throws EntityException
74 {
75 if (UtilTimerStack.isActive())
76 UtilTimerStack.push(this.getClass().getName() + "_findUsers");
77
78 String parsedQuery = null;
79 Pager iter;
80 parsedQuery = directQuery(query, parsedQuery);
81
82 LDAPPagerInfo info = userAdaptor.search(new LiteralFilter(parsedQuery));
83 iter = new LDAPEntityPager(repository, userFactory, info);
84 DefaultSearchResult searchResult = new DefaultSearchResult(iter, repositoryIdentifier.getKey());
85
86 if (UtilTimerStack.isActive())
87 UtilTimerStack.pop(this.getClass().getName() + "_findUsers");
88
89 return searchResult;
90 }
91
92 public com.atlassian.user.search.SearchResult findGroups(Query query) throws EntityException
93 {
94 String parsedQuery = directQuery(query, null);
95 LDAPPagerInfo info = groupAdaptor.search(new LiteralFilter(parsedQuery));
96 Pager pager = new LDAPEntityPager(repository, groupFactory, info);
97 return new DefaultSearchResult(pager, repositoryIdentifier.getKey());
98 }
99
100 public SearchResult findUsers(Query query, QueryContext context) throws EntityException
101 {
102 SearchResult result = null;
103
104 if (context.getRepositoryKeys().contains(repositoryIdentifier.getKey()) ||
105 context.getRepositoryKeys().contains(QueryContext.ALL_REPOSITORIES))
106 {
107 result = findUsers(query);
108 }
109
110 if (result == null)
111 return null;
112
113 return result;
114 }
115
116 public SearchResult findGroups(Query query, QueryContext context) throws EntityException
117 {
118 SearchResult result = null;
119
120 if (context.getRepositoryKeys().contains(repositoryIdentifier.getKey()) ||
121 context.getRepositoryKeys().contains(QueryContext.ALL_REPOSITORIES))
122 {
123 result = findGroups(query);
124 }
125
126 if (result == null)
127 return null;
128
129 return result;
130 }
131
132 private String directQuery(Query query, String defaultQuery) throws EntityException
133 {
134 if (query instanceof TermQuery)
135 {
136 StringBuffer parsedQueryStringBuffer = parseQuery((TermQuery) query);
137 return parsedQueryStringBuffer.toString();
138 }
139 else if (query instanceof BooleanQuery)
140 {
141 return parseQuery((BooleanQuery) query).toString();
142 }
143
144 return defaultQuery;
145 }
146
147 public StringBuffer parseQuery(BooleanQuery query) throws EntityException
148 {
149 StringBuffer parsedClause = new StringBuffer();
150 parsedClause.append(OPEN_PARAN);
151
152 if (query.isAND())
153 parsedClause.append(AND);
154 else
155 parsedClause.append(OR);
156
157 Iterator queryIter = query.getQueries().iterator();
158
159 while (queryIter.hasNext())
160 {
161 Query foundQuery = (Query) queryIter.next();
162 if (foundQuery instanceof BooleanQuery)
163 parsedClause.append(parseQuery((BooleanQuery) foundQuery));
164 else
165 parsedClause.append(parseQuery((TermQuery) foundQuery));
166 }
167
168 parsedClause.append(CLOSE_PARAN);
169
170 return parsedClause;
171 }
172
173 public StringBuffer parseQuery(TermQuery q) throws EntityException
174 {
175 StringBuffer parsedQuery = null;
176
177 if (q instanceof UserNameTermQuery)
178 parsedQuery = parseTermQuery(q, searchProperties.getUsernameAttribute());
179 else if (q instanceof GroupNameTermQuery)
180 parsedQuery = parseTermQuery(q, searchProperties.getGroupnameAttribute());
181 else if (q instanceof EmailTermQuery)
182 parsedQuery = parseTermQuery(q, searchProperties.getEmailAttribute());
183 else if (q instanceof FullNameTermQuery)
184 parsedQuery = parseFullNameTermQuery(q);
185 else if (q instanceof UsersInGroupTwoTermQuery)
186 parsedQuery = parseMemberNamesInGroupTermQuery();
187 else if (q instanceof GroupsOfUserTwoTermQuery)
188 parsedQuery = parseGroupsOfUserTwoTermQuery((GroupsOfUserTwoTermQuery) q);
189
190 return parsedQuery;
191 }
192
193 private StringBuffer parseFullNameTermQuery(TermQuery q)
194 {
195 StringBuffer query = new StringBuffer();
196
197 query.insert(0, OR);
198 query.insert(0, "(");
199 query.append(parseTermQuery(q, searchProperties.getFirstnameAttribute()));
200 query.append(parseTermQuery(q, searchProperties.getSurnameAttribute()));
201 query.append(")");
202
203 return query;
204 }
205
206 /**
207 * Looks up membership lists associated with the group indicated by {@link UsersInGroupTwoTermQuery} and returns an
208 * ldap getGroupEntries string capable of returning their dns.
209 */
210 private StringBuffer parseMemberNamesInGroupTermQuery() throws EntityException
211 {
212 if (!membershipProperties.isMembershipAttributeOnGroup())
213 throw new UnsupportedOperationException();
214
215 LDAPPagerInfo pagerInfo = groupAdaptor.getGroupEntries(null);
216 NamingEnumeration enume = pagerInfo.getNamingEnumeration();
217 return parseMembershipAttributesForGroupNames(enume);
218 }
219
220 /**
221 * @return StringBuffer holding an LDAP getGroupEntries query which will return a list of the user's groups.
222 */
223 private StringBuffer parseGroupsOfUserTwoTermQuery(GroupsOfUserTwoTermQuery q) throws EntityException
224 {
225 String userPattern = null;
226
227 UserNameTermQuery uQuery = q.getUserNameTermQuery();
228 if (uQuery.isMatchingSubstring())
229 {
230 if (uQuery.getMatchingRule().equals(TermQuery.SUBSTRING_STARTS_WITH))
231 userPattern = uQuery.getTerm() + WILDCARD;
232 else if (uQuery.getMatchingRule().equals(TermQuery.SUBSTRING_ENDS_WITH))
233 userPattern = WILDCARD + uQuery.getTerm();
234 else if (uQuery.getMatchingRule().equals(TermQuery.SUBSTRING_CONTAINS))
235 userPattern = WILDCARD + uQuery.getTerm() + WILDCARD;
236 }
237 else
238 userPattern = uQuery.getTerm();
239
240
241 LDAPPagerInfo pagerInfo = groupAdaptor.getGroupEntriesViaMembership(userPattern);
242
243 StringBuffer groupQuery;
244
245 if (membershipProperties.isMembershipAttributeOnGroup())
246 groupQuery = buildQueryForUsersInStaticGroups(pagerInfo.getNamingEnumeration());
247 else
248 groupQuery = buildQueryForUsersInDynamicGroups(pagerInfo.getNamingEnumeration());
249
250 return groupQuery;
251 }
252
253 private StringBuffer buildQueryForUsersInDynamicGroups(NamingEnumeration enume) throws EntityException
254 {
255 StringBuffer parsedQuery = null;
256
257 while (enume.hasMoreElements())
258 {
259 javax.naming.directory.SearchResult result = (javax.naming.directory.SearchResult) enume.nextElement();
260 Attributes attrs = result.getAttributes();
261
262 Attribute attr = attrs.get(membershipProperties.getMembershipAttribute());
263
264 if (attr == null)
265 return null;
266
267 try
268 {
269 NamingEnumeration membershipEnume = attr.getAll();
270 while (membershipEnume.hasMoreElements())
271 {
272 boolean wrapping = false;
273
274 if (parsedQuery == null)
275 {
276 parsedQuery = new StringBuffer();
277 }
278 else
279 {
280 parsedQuery.insert(0, OR);
281 parsedQuery.insert(0, OPEN_PARAN);
282 wrapping = true;
283 }
284
285 parsedQuery.append(OPEN_PARAN);
286 parsedQuery.append(((String) membershipEnume.nextElement()).split(",")[0]);
287 parsedQuery.append(CLOSE_PARAN);
288
289 if (wrapping)
290 parsedQuery.append(CLOSE_PARAN);
291 }
292 }
293 catch (Exception e)
294 {
295 throw new RepositoryException(e);
296 }
297 }
298
299 return parsedQuery;
300 }
301
302 private StringBuffer buildQueryForUsersInStaticGroups(NamingEnumeration enume) throws EntityException
303 {
304 StringBuffer parsedQuery = null;
305
306 while (enume.hasMoreElements())
307 {
308 javax.naming.directory.SearchResult result = (javax.naming.directory.SearchResult) enume.nextElement();
309 Attributes attrs = result.getAttributes();
310
311 Attribute attr = attrs.get(searchProperties.getGroupnameAttribute());
312
313 if (attr == null)
314 return null;
315
316 try
317 {
318 NamingEnumeration membershipEnume = attr.getAll();
319 while (membershipEnume.hasMoreElements())
320 {
321 boolean wrapping = false;
322
323 if (parsedQuery == null)
324 {
325 parsedQuery = new StringBuffer();
326 }
327 else
328 {
329 parsedQuery.insert(0, OR);
330 parsedQuery.insert(0, OPEN_PARAN);
331 wrapping = true;
332 }
333
334 parsedQuery.append(OPEN_PARAN);
335 parsedQuery.append(searchProperties.getGroupnameAttribute());
336 parsedQuery.append(EQ);
337 parsedQuery.append(((String) membershipEnume.nextElement()).split(",")[0]);
338 parsedQuery.append(CLOSE_PARAN);
339
340 if (wrapping)
341 parsedQuery.append(CLOSE_PARAN);
342 }
343 }
344 catch (Exception e)
345 {
346 throw new RepositoryException(e);
347 }
348 }
349
350 return parsedQuery;
351 }
352
353 private StringBuffer parseMembershipAttributesForGroupNames(NamingEnumeration enume)
354 {
355 StringBuffer parsedQuery = null;
356
357 while (enume.hasMoreElements())
358 {
359 javax.naming.directory.SearchResult result = (javax.naming.directory.SearchResult) enume.nextElement();
360 Attributes attrs = result.getAttributes();
361
362 Attribute attr;
363
364 if (membershipProperties.isMembershipAttributeOnGroup())
365 attr = attrs.get(membershipProperties.getMembershipAttribute());
366 else
367 attr = attrs.get(searchProperties.getGroupnameAttribute());
368
369 try
370 {
371 if (parsedQuery == null)
372 parsedQuery = parseMembershipAttributeForGroupNames(attr);
373 else
374 {
375 parsedQuery.insert(0, "(|");
376 parsedQuery = parsedQuery.append(parseMembershipAttributeForGroupNames(attr));
377 parsedQuery.append(")");
378 }
379 }
380 catch (Exception e)
381 {
382 e.printStackTrace();
383 }
384 }
385 return parsedQuery;
386 }
387
388 private StringBuffer parseMembershipAttributeForGroupNames(Attribute attr) throws NamingException
389 {
390 if (attr == null)
391 return null;
392
393 StringBuffer parsedQuery = null;
394 NamingEnumeration membershipEnume = attr.getAll();
395 while (membershipEnume.hasMoreElements())
396 {
397 boolean wrapping = false;
398
399 if (parsedQuery == null)
400 {
401 parsedQuery = new StringBuffer();
402 }
403 else
404 {
405 parsedQuery.insert(0, OR);
406 parsedQuery.insert(0, OPEN_PARAN);
407 wrapping = true;
408 }
409
410 if (membershipProperties.isMembershipAttributeOnGroup())
411 {
412 parsedQuery.append(OPEN_PARAN);
413 parsedQuery.append(((String) membershipEnume.nextElement()).split(",")[0]);
414 parsedQuery.append(CLOSE_PARAN);
415 }
416 else
417 {
418 parsedQuery.append(OPEN_PARAN);
419 parsedQuery.append(searchProperties.getGroupnameAttribute());
420 parsedQuery.append(EQ);
421 parsedQuery.append(((String) membershipEnume.nextElement()).split(",")[0]);
422 parsedQuery.append(CLOSE_PARAN);
423
424 }
425
426 if (wrapping)
427 parsedQuery.append(CLOSE_PARAN);
428 }
429
430 return parsedQuery;
431 }
432
433 /**
434 * Generic method which can parse a term query and relate it to a targeted attributeType in LDAP speak.
435 * <p/>
436 * If attributeType is 'cn' and q.getTerm() is 'fred' then it will return a {@link StringBuffer} holding (cn=fred).
437 * It will also deal with substring matching rules. If the {@link TermQuery} was matching substrings using {@link
438 * TermQuery#SUBSTRING_STARTS_WITH}, using the example values above, it would return (cn=fred*)
439 */
440 private StringBuffer parseTermQuery(TermQuery q, String attributeType)
441 {
442 StringBuffer parsedQuery;
443 parsedQuery = new StringBuffer();
444 parsedQuery.append(OPEN_PARAN);
445 parsedQuery.append(attributeType);
446 parsedQuery.append(EQ);
447
448 if (q.isMatchingSubstring())
449 {
450 if ((q.getMatchingRule().equals(TermQuery.SUBSTRING_ENDS_WITH)) ||
451 (q.getMatchingRule().equals(TermQuery.SUBSTRING_CONTAINS)))
452 {
453 parsedQuery.append(WILDCARD);
454 }
455 }
456
457 parsedQuery.append(LdapEncoder.filterEncode(q.getTerm()));
458
459 if (q.isMatchingSubstring())
460 {
461 if ((q.getMatchingRule().equals(TermQuery.SUBSTRING_STARTS_WITH)) ||
462 (q.getMatchingRule().equals(TermQuery.SUBSTRING_CONTAINS)))
463 {
464 parsedQuery.append(WILDCARD);
465 }
466 }
467
468 parsedQuery.append(CLOSE_PARAN);
469 return parsedQuery;
470 }
471 }