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.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         //First we find the membership information for the user via the groupAdaptor
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 }