View Javadoc

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