View Javadoc

1   package com.atlassian.user.impl.ldap.adaptor;
2   
3   import com.atlassian.user.EntityException;
4   import com.atlassian.user.Group;
5   import com.atlassian.user.impl.EntityMissingException;
6   import com.atlassian.user.impl.RepositoryException;
7   import com.atlassian.user.impl.ldap.LDAPEntity;
8   import com.atlassian.user.impl.ldap.LDAPGroupFactory;
9   import com.atlassian.user.impl.ldap.properties.LdapSearchProperties;
10  import com.atlassian.user.impl.ldap.repository.LdapContextFactory;
11  import com.atlassian.user.impl.ldap.search.LDAPPagerInfo;
12  import com.atlassian.user.impl.ldap.search.LdapFilterFactory;
13  import com.atlassian.user.impl.ldap.search.page.LDAPEntityPager;
14  import com.atlassian.user.search.page.Pager;
15  import com.atlassian.user.util.LDAPUtils;
16  import com.atlassian.util.profiling.UtilTimerStack;
17  import com.opensymphony.util.TextUtils;
18  import net.sf.ldaptemplate.support.filter.EqualsFilter;
19  import net.sf.ldaptemplate.support.filter.Filter;
20  import org.apache.log4j.Logger;
21  
22  import javax.naming.NamingEnumeration;
23  import javax.naming.NamingException;
24  import javax.naming.directory.Attributes;
25  import javax.naming.directory.DirContext;
26  import javax.naming.directory.SearchControls;
27  import javax.naming.directory.SearchResult;
28  import java.text.MessageFormat;
29  
30  public abstract class AbstractLDAPGroupAdaptor implements LDAPGroupAdaptor
31  {
32      protected final Logger log = Logger.getLogger(this.getClass());
33  
34      protected final LdapSearchProperties searchProperties;
35      private final LdapFilterFactory filterFactory;
36  
37      protected final LdapContextFactory repository;
38      protected final LDAPGroupFactory groupFactory;
39  
40      protected AbstractLDAPGroupAdaptor(LdapContextFactory repo, LdapSearchProperties searchProperties,
41          LDAPGroupFactory groupFactory, LdapFilterFactory filterFactory)
42      {
43          this.repository = repo;
44          this.searchProperties = searchProperties;
45          this.filterFactory = filterFactory;
46          this.groupFactory = groupFactory;
47      }
48  
49      public Group getGroup(String name) throws EntityException
50      {
51          DirContext ctx = null;
52          NamingEnumeration<SearchResult> enume;
53          Group group = null;
54  
55          String filter = constructGroupSearchFilter(name).encode();
56          String baseDn = searchProperties.getBaseGroupNamespace();
57  
58          try
59          {
60              ctx = repository.getLDAPContext();
61              if (log.isDebugEnabled())
62                  log.debug("AbstractLDAPGroupAdapter.getGroup:" + filter);
63  
64              enume = ctx.search(baseDn, filter,
65                  LDAPUtils.createSearchControls(new String[]{searchProperties.getGroupnameAttribute()}, searchProperties.isGroupSearchScopeAllDepths(),
66                      searchProperties.getTimeLimitMillis()));
67              if (enume == null) return null;
68  
69              while (enume.hasMoreElements())
70              {
71                  SearchResult result = enume.nextElement();
72                  Attributes attrs = result.getAttributes();
73                  group = groupFactory.getGroup(attrs, result.getName());
74              }
75          }
76          catch (NamingException e)
77          {
78              String msg = "Exception when retrieving LDAP group {0} (base DN: {1}, filter: {2})";
79              throw new RepositoryException(MessageFormat.format(msg, name, baseDn, filter), e);
80          }
81          finally
82          {
83              try
84              {
85                  if (ctx != null) ctx.close();
86              }
87              catch (NamingException e)
88              {
89                  log.warn("Failed to close LDAP connection after search for group: " + name, e);
90              }
91          }
92  
93          return group;
94      }
95  
96      public Pager<Group> getGroups() throws EntityException
97      {
98          LDAPPagerInfo ldapPagerInfo = getGroupEntries();
99  
100         return new LDAPEntityPager<Group>(repository, groupFactory, ldapPagerInfo);
101     }
102 
103     public LDAPPagerInfo getGroupEntries() throws EntityException
104     {
105         return getGroupEntries("*");
106     }
107 
108     public LDAPPagerInfo getGroupEntries(String groupName) throws EntityException
109     {
110         return getGroupEntries(groupName, null, null);
111     }
112 
113     public LDAPPagerInfo getGroupEntries(String[] attributesToReturn, Filter additionalSearchFilter)
114             throws EntityException
115     {
116         return getGroupEntries("*", attributesToReturn, additionalSearchFilter);
117     }
118 
119     /**
120      * @param groupName a String holding an RFC 2254 compliant getGroupEntries parameter to AND to
121      *                  the {@link com.atlassian.user.impl.ldap.repository.LdapContextFactory#GROUP_SEARCH_FILTER}.
122      * @throws RepositoryException
123      */
124     public LDAPPagerInfo getGroupEntries(String groupName, String[] attributesToReturn, Filter additionalSearchFilter)
125             throws RepositoryException
126     {
127         Filter searchFilter = constructGroupSearchFilter(groupName, additionalSearchFilter);
128         return search(searchFilter, attributesToReturn);
129     }
130 
131     /**
132      * @return an {@link LDAPPagerInfo} holding the search query used, the result, and the original base context.
133      * @throws RepositoryException
134      */
135     public LDAPPagerInfo search(Filter searchFilter) throws RepositoryException
136     {
137         return search(searchFilter, null);
138     }
139 
140     /**
141      * @param attributesToReturn - will default to mapped membership attribute available in the {@link com.atlassian.user.impl.ldap.repository.LdapContextFactory}
142      *                           if set to null.
143      * @return an {@link LDAPPagerInfo} holding the search query used, the result, and the original base context.
144      * @throws RepositoryException
145      */
146     public LDAPPagerInfo search(Filter filter, String[] attributesToReturn) throws RepositoryException
147     {
148         if (UtilTimerStack.isActive())
149             UtilTimerStack.push(this.getClass().getName() + "_search(" + filter + ")");
150 
151         try
152         {
153             if (attributesToReturn == null)
154                 attributesToReturn = new String[]{searchProperties.getGroupnameAttribute()};
155 
156             Filter groupSearchFilter = filterFactory.getGroupSearchFilter();
157             if (filter != null)
158                 groupSearchFilter = LDAPUtils.makeAndFilter(groupSearchFilter, filter);
159 
160             SearchControls ctls = LDAPUtils.createSearchControls(
161                 attributesToReturn, searchProperties.isGroupSearchScopeAllDepths(),
162                 searchProperties.getTimeLimitMillis());
163             NamingEnumeration<SearchResult> groupSearchEnume = null;
164 
165             if (UtilTimerStack.isActive())
166                 UtilTimerStack.push(this.getClass().getName() + "_search_JNDI_RAW_(" + groupSearchFilter + ")");
167             DirContext ctx = null;
168             try
169             {
170                 ctx = repository.getLDAPContext();
171 
172                 log.debug("Searching for groups using base name space:" + searchProperties.getBaseGroupNamespace() +
173                     " and encoded filter " + groupSearchFilter.encode());
174 
175                 groupSearchEnume = ctx.search(searchProperties.getBaseGroupNamespace(), groupSearchFilter.encode(), ctls);
176 
177                 if (groupSearchEnume.hasMore())
178                     log.debug("found at least one group");
179                 else
180                     log.debug("no groups found");
181             }
182             catch (NamingException e)
183             {
184                 throw new RepositoryException(e);
185             }
186             finally
187             {
188                 if (UtilTimerStack.isActive())
189                     UtilTimerStack.pop(this.getClass().getName() + "_search_JNDI_RAW_(" + groupSearchFilter + ")");
190 
191                 try
192                 {
193                     if (ctx != null) ctx.close();
194                 }
195                 catch (NamingException e)
196                 {
197                     log.warn("Exception trying to close LDAP context, possible resource leak", e);
198                 }
199             }
200 
201             return new LDAPPagerInfo(groupSearchEnume, groupSearchFilter,
202                 searchProperties.getBaseGroupNamespace(), searchProperties.isGroupSearchScopeAllDepths(),
203                 attributesToReturn, searchProperties.getTimeLimitMillis());
204         }
205         finally
206         {
207             if (UtilTimerStack.isActive())
208                 UtilTimerStack.pop(this.getClass().getName() + "_search(" + filter + ")");
209         }
210     }
211 
212     protected Filter constructGroupSearchFilter(String name)
213     {
214         return constructGroupSearchFilter(name, null);
215     }
216 
217     protected Filter constructGroupSearchFilter(String name, Filter patternToAnd)
218     {
219         Filter searchFilter = null;
220 
221         if (TextUtils.stringSet(name) && !"*".equals(name)) // if we are only searching for *, then we can exclude the group name attribute from the search
222             searchFilter = new EqualsFilter(searchProperties.getGroupnameAttribute(), name);
223 
224         searchFilter = addGroupSearchFilter(searchFilter);
225 
226         if (patternToAnd != null)
227             return LDAPUtils.makeAndFilter(searchFilter, patternToAnd);
228 
229         return searchFilter;
230     }
231 
232     private Filter addGroupSearchFilter(Filter searchFilter)
233     {
234         return LDAPUtils.makeAndFilter(searchFilter, filterFactory.getGroupSearchFilter());
235     }
236 
237     /**
238      * Extracts the username from the first occurrence of the username attribute
239      * <br/>
240      * For example,
241      * <p/>
242      * CN=ldapusera,CN=Users,DC=atlassian,DC=private
243      * </p>
244      * becomes ldapusera
245      *
246      * @return String representing the username in the DN
247      */
248     protected String getFirstPhraseFromDN(String dn)
249     {
250         String[] rdns = dn.split(",");
251         String[] firstPhrase = rdns[0].split("=");
252 
253         return firstPhrase[1];
254     }
255 
256 
257     public String getGroupDN(String groupName) throws EntityException
258     {
259         LDAPPagerInfo ldapPagerInfo = getGroupEntries(groupName, new String[]{"dn"}, null);
260 
261         if (!ldapPagerInfo.getNamingEnumeration().hasMoreElements())
262             throw new EntityMissingException("Could not get DN for group [" + groupName + "]");
263 
264         SearchResult result = (SearchResult) ldapPagerInfo.getNamingEnumeration().nextElement();
265 
266         String groupDN = result.getName();
267         //we need a DN. The result only returns the full DN if the result was in the base getGroupEntries context
268         if (groupDN.indexOf(searchProperties.getBaseGroupNamespace()) == -1)
269             groupDN = groupDN + "," + searchProperties.getBaseGroupNamespace();
270 
271         return groupDN;
272     }
273 
274     public String getGroupDN(Group group) throws EntityException
275     {
276         if (group instanceof LDAPEntity)
277         {
278             LDAPEntity entity = (LDAPEntity) group;
279             return entity.getDistinguishedName();
280         }
281         else
282             throw new IllegalArgumentException("Group is not an LDAPEntity");
283     }
284 
285     public LDAPGroupFactory getGroupFactory()
286     {
287         return groupFactory;
288     }
289 }