001package com.hfg.ldap;
002
003
004import java.lang.reflect.Method;
005import java.net.URI;
006import java.security.cert.X509Certificate;
007import java.util.*;
008import java.util.logging.Level;
009import java.util.logging.Logger;
010import javax.naming.AuthenticationException;
011import javax.naming.Context;
012import javax.naming.InvalidNameException;
013import javax.naming.NamingEnumeration;
014import javax.naming.NamingException;
015import javax.naming.directory.Attribute;
016import javax.naming.directory.Attributes;
017import javax.naming.directory.DirContext;
018import javax.naming.directory.SearchControls;
019import javax.naming.directory.SearchResult;
020import javax.naming.ldap.Control;
021import javax.naming.ldap.InitialLdapContext;
022import javax.naming.ldap.LdapContext;
023import javax.naming.ldap.LdapName;
024import javax.naming.ldap.PagedResultsControl;
025import javax.naming.ldap.PagedResultsResponseControl;
026import javax.net.ssl.SSLHandshakeException;
027
028import com.hfg.cert.CertificateUtil;
029import com.hfg.exception.InvalidValueException;
030import com.hfg.html.Col;
031import com.hfg.ldap.ad.ActiveDirectory;
032import com.hfg.security.LoginCredentials;
033import com.hfg.util.StackTraceUtil;
034import com.hfg.util.StringBuilderPlus;
035import com.hfg.util.StringUtil;
036import com.hfg.util.collection.CollectionUtil;
037
038import static com.hfg.cert.CertificateUtil.geLdapCertificates;
039
040//------------------------------------------------------------------------------
041/**
042 Simple LDAP Client.
043 <div>
044  @author J. Alex Taylor, hairyfatguy.com
045 </div>
046 */
047//------------------------------------------------------------------------------
048// com.hfg XML/HTML Coding Library
049//
050// This library is free software; you can redistribute it and/or
051// modify it under the terms of the GNU Lesser General Public
052// License as published by the Free Software Foundation; either
053// version 2.1 of the License, or (at your option) any later version.
054//
055// This library is distributed in the hope that it will be useful,
056// but WITHOUT ANY WARRANTY; without even the implied warranty of
057// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
058// Lesser General Public License for more details.
059//
060// You should have received a copy of the GNU Lesser General Public
061// License along with this library; if not, write to the Free Software
062// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
063//
064// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
065// jataylor@hairyfatguy.com
066//------------------------------------------------------------------------------
067
068public class LDAPClient<U extends LDAP_User>
069{
070
071   private static final ReferralHandling sDefaultReferralHandling = ReferralHandling.FOLLOW_REFERRALS;
072
073   private static final Logger LOGGER = Logger.getLogger(LDAPClient.class.getPackage().getName());
074
075
076   private String   mLDAP_ServerURL;
077   private LdapName mLDAP_UserContext;
078   private LdapName mLDAP_GroupContext;
079   private String   mLDAP_PrincipalFieldName = "uid";
080   private LdapName mLDAP_PrincipalContext;
081   private String   mLDAP_PrincipalSuffix;
082   private LoginCredentials mLDAP_PrincipalCredentials;
083   private LDAP_UserObjFactory mUserFactory = new LDAP_UserFactory();
084   private ReferralHandling mReferralHandling = sDefaultReferralHandling;
085
086   private Set<String> mSupportedControls;
087
088   private static final String PAGING_OID = "1.2.840.113556.1.4.319";
089   private static final String VLV_OID    = "2.16.840.1.113730.3.4.9";
090   
091   public enum ReferralHandling
092   {
093      FOLLOW_REFERRALS("follow"),
094      IGNORE_REFERRALS("ignore"),
095      THROW_REFERRALS("throw");
096
097      private String mValue;
098
099      private ReferralHandling(String inValue)
100      {
101         mValue = inValue;
102      }
103
104      public String getValue()
105      {
106         return mValue;
107      }
108
109      @Override
110      public String toString()
111      {
112         return getValue();
113      }
114   }
115
116   static
117   {
118      LOGGER.setLevel(Level.WARNING);
119      LOGGER.setUseParentHandlers(true);
120   }
121   
122   //###########################################################################
123   // CONSTRUCTORS
124   //###########################################################################
125
126   //------------------------------------------------------------------------
127   public LDAPClient()
128   {
129      
130   }
131
132   //------------------------------------------------------------------------
133   public LDAPClient(LDAP_Config inConfig)
134   {
135      this();
136   
137      setServerURL(inConfig.getServerURL());
138      setUserContext(inConfig.getUserContext());
139      setPrincipalFieldName(inConfig.getPrincipalFieldName());
140      setPrincipalCredentials(inConfig.getPrincipalCredentials());
141      setReferralHandling(inConfig.getReferralHandling());
142   }
143   
144   //###########################################################################
145   // PUBLIC METHODS
146   //###########################################################################
147
148   //---------------------------------------------------------------------------
149   public static Logger getLogger()
150   {
151      return LOGGER;
152   }
153
154   //---------------------------------------------------------------------------
155   public LDAPClient<U> setServerURL(String inValue)
156   {
157      mLDAP_ServerURL = inValue;
158      return this;
159   }
160   
161   //---------------------------------------------------------------------------
162   public String getServerURL()
163   {
164      return mLDAP_ServerURL;
165   }
166
167   //---------------------------------------------------------------------------
168   public LDAPClient<U> setUserContext(String inValue)
169   {
170      try
171      {
172         setUserContext(inValue != null ? new LdapName(inValue) : null);
173      }
174      catch (InvalidNameException e)
175      {
176         throw new RuntimeException(e);
177      }
178
179      return this;
180   }
181
182   //---------------------------------------------------------------------------
183   public LDAPClient<U> setReferralHandling(ReferralHandling inValue)
184   {
185      mReferralHandling = inValue;
186      return this;
187   }
188
189   //---------------------------------------------------------------------------
190   public ReferralHandling getReferralHandling()
191   {
192      return mReferralHandling;
193   }
194
195
196   //---------------------------------------------------------------------------
197   public LDAPClient<U> setUserContext(LdapName inValue)
198   {
199      mLDAP_UserContext = inValue;
200      return this;
201   }
202
203
204   //---------------------------------------------------------------------------
205   public LDAPClient<U> setGroupContext(String inValue)
206   {
207      try
208      {
209         setGroupContext(inValue != null ? new LdapName(inValue) : null);
210      }
211      catch (InvalidNameException e)
212      {
213         throw new RuntimeException(e);
214      }
215
216      return this;
217   }
218
219   //---------------------------------------------------------------------------
220   public LDAPClient<U> setGroupContext(LdapName inValue)
221   {
222      mLDAP_GroupContext = inValue;
223      return this;
224   }
225
226
227   //---------------------------------------------------------------------------
228   public LDAPClient<U> setPrincipalContext(String inValue)
229   {
230      try
231      {
232         setPrincipalContext(inValue != null ? new LdapName(inValue) : null);
233      }
234      catch (InvalidNameException e)
235      {
236         throw new RuntimeException(e);
237      }
238
239      return this;
240   }
241
242   //---------------------------------------------------------------------------
243   public LDAPClient<U> setPrincipalContext(LdapName inValue)
244   {
245      mLDAP_PrincipalContext = inValue;
246      return this;
247   }
248
249   //---------------------------------------------------------------------------
250   public LDAPClient<U> setPrincipalSuffix(String inValue)
251   {
252      mLDAP_PrincipalSuffix = inValue;
253      return this;
254   }
255
256   //---------------------------------------------------------------------------
257   /**
258    Specifies the credentials to be used to access the LDAP server.
259    * @param inValue the security principal credentials
260    * @return this LDAPClient object to facilitate method chaining
261    */
262   public LDAPClient<U> setPrincipalCredentials(LoginCredentials inValue)
263   {
264      mLDAP_PrincipalCredentials = inValue;
265      return this;
266   }
267
268   //---------------------------------------------------------------------------
269   public LDAPClient<U> setPrincipalFieldName(String inValue)
270   {
271      mLDAP_PrincipalFieldName = inValue;
272      return this;
273   }
274
275   //---------------------------------------------------------------------------
276   public LDAP_UserObjFactory<U> getUserFactory()
277   {
278      return mUserFactory;
279   }
280
281   //---------------------------------------------------------------------------
282   public LDAPClient<U> setUserFactory(LDAP_UserObjFactory<U> inValue)
283   {
284      if (null == inValue)
285      {
286         throw new InvalidValueException("The user factory cannot be set to null!");
287      }
288
289      mUserFactory = inValue;
290      return this;
291   }
292
293   //---------------------------------------------------------------------------
294   // TODO: Should this method return a more complex result that could contain more info about failures?
295   public boolean authenticate(LoginCredentials inCredentials)
296   {
297      crosscheck();
298
299      boolean authenticated = false;
300
301      DirContext ctx = null;
302      try
303      {
304         // Create the initial context
305         ctx = getInitialDirContext(inCredentials);
306         if (ctx != null)
307         {
308            authenticated = true;
309         }
310/*
311         // get more attributes about this user
312         SearchControls scs = new SearchControls();
313         scs.setSearchScope(SearchControls.SUBTREE_SCOPE);
314         String[] attrNames = { "mail", "cn" };
315         scs.setReturningAttributes(attrNames);
316
317         NamingEnumeration nes = ctx.search(mLDAP_UserContext, "uid=" + userName, scs);
318         if(nes.hasMore())
319         {
320            Attributes attrs = ((SearchResult) nes.next()).getAttributes();
321            System.out.println("mail: " + attrs.get("mail").get());
322            System.out.println("cn: " + attrs.get("cn").get());
323         }
324*/
325      }
326      catch (AuthenticationException e)
327      {
328         // If it looks like an Active Directory exception, try to decode it...
329         String msg = ActiveDirectory.decodeAuthenticationException(e);
330         if (StringUtil.isSet(msg))
331         {
332            new LDAP_Exception(msg, e).printStackTrace();
333         }
334         else
335         {
336            e.printStackTrace();
337         }
338      }
339      catch (Exception e)
340      {
341         e.printStackTrace();
342      }
343      finally
344      {
345         if (ctx != null)
346         {
347            try
348            {
349               ctx.close();
350            }
351            catch (NamingException e)
352            {
353               // Ignore
354            }
355         }
356      }
357
358      return authenticated;
359   }
360
361   //---------------------------------------------------------------------------
362   public U getInfoForUser(String inQuery)
363   {
364      return getInfoForUser(inQuery, null);
365   }
366
367   //---------------------------------------------------------------------------
368   public U getInfoForUser(String inQuery, List<LDAP_UserField> inFields)
369   {
370      return getInfoForUser(null, inQuery, inFields);
371   }
372
373   //---------------------------------------------------------------------------
374   public U getInfoForUser(LdapName inBaseDN, String inQuery)
375   {
376      return getInfoForUser(inBaseDN, inQuery, null);
377   }
378
379   //---------------------------------------------------------------------------
380   public U getInfoForUser(LdapName inBaseDN, String inQuery, List<LDAP_UserField> inFields)
381   {
382      List<U> users = getInfoForUsers(inBaseDN, inQuery, inFields);
383      return (CollectionUtil.hasValues(users) ? users.get(0) : null);
384   }
385
386   //---------------------------------------------------------------------------
387   public List<U> getInfoForUsers(String inQuery)
388   {
389      return getInfoForUsers(inQuery, null);
390   }
391
392   //---------------------------------------------------------------------------
393   public List<U> getInfoForUsers(String inFilter, List<LDAP_UserField> inFields)
394   {
395      return getInfoForUsers(null, inFilter, inFields);
396   }
397
398   //---------------------------------------------------------------------------
399   public List<U> getInfoForUsers(LdapName inBaseDN, String inFilter, List<LDAP_UserField> inFields)
400   {
401      List<U> users;
402
403      LdapContext ctx = null;
404      try
405      {
406         // Create the initial context
407         ctx = getInitialDirContext(mLDAP_PrincipalCredentials);
408
409         if (isPagingSupported(ctx))
410         {
411            users = getInfoForUsersWithPaging(ctx, inBaseDN, inFilter, inFields);
412         }
413         else
414         {
415            users = getInfoForUsersWithoutPaging(ctx, inBaseDN, inFilter, inFields);
416         }
417      }
418      catch (Exception e)
419      {
420         throw composeException(e);
421      }
422      finally
423      {
424         if (ctx != null)
425         {
426            try
427            {
428               ctx.close();
429            }
430            catch (NamingException e)
431            {
432
433            }
434         }
435      }
436
437      int numUsersFound = (CollectionUtil.hasValues(users) ? users.size() : 0);
438      LOGGER.fine(numUsersFound + " user" + (numUsersFound != 1 ? "s" : "") + " found by LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
439
440      return users;
441   }
442
443   //---------------------------------------------------------------------------
444   public List<U> getInfoForUsersWithPaging(LdapContext inCtx, LdapName inBaseDN, String inFilter, List<LDAP_UserField> inFields)
445      throws Exception
446   {
447      List<U> users = null;
448
449      LdapName baseDN = inBaseDN;
450      if (null == baseDN)
451      {
452         baseDN = mLDAP_UserContext; // Use the default user context
453      }
454
455      Map<String, LDAP_UserField> fieldMap = null;
456
457      // get more attributes about this user
458      SearchControls searchControls = new SearchControls();
459      searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
460
461      String[] attributesToRetrieve = new String[] {"*"}; // Default to all attributes
462      if (CollectionUtil.hasValues(inFields))
463      {
464         fieldMap = new HashMap<>(inFields.size());
465         List<String> attrNames = new ArrayList<>(inFields.size());
466         for (LDAP_UserField field : inFields)
467         {
468            attrNames.add(field.name());
469            fieldMap.put(field.name(), field);
470         }
471
472         attributesToRetrieve = attrNames.toArray(new String[] {});
473      }
474      
475      
476      searchControls.setReturningAttributes(attributesToRetrieve);
477      LOGGER.fine("LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
478
479      // Activate paged results
480      int pageSize = 1000; // 1000 entries per page
481      byte[] cookie = null;
482      int total;
483      inCtx.setRequestControls(new Control[]{
484            new PagedResultsControl(pageSize, Control.CRITICAL) });
485      do
486      {
487         // Perform the search
488         NamingEnumeration results = inCtx.search(baseDN, inFilter, searchControls);
489         while (results != null
490               && results.hasMore())
491         {
492            if (null == users)
493            {
494               users = new ArrayList<>();
495            }
496
497            U user = getUserFactory().createUserObj();
498            users.add(user);
499
500            Attributes                attrs    = ((SearchResult) results.next()).getAttributes();
501            NamingEnumeration<String> attrEnum = attrs.getIDs();
502            while (attrEnum.hasMoreElements())
503            {
504               String attrID = attrEnum.nextElement();
505               if (fieldMap != null)
506               {
507                  LDAP_UserField field = fieldMap.get(attrID);
508                  if (field != null)
509                  {
510                     Method setter = field.getUserObjectSetter();
511                     if (setter != null)
512                     {
513                        try
514                        {
515                           setter.invoke(user, attrs.get(attrID)
516                                                    .get()
517                                                    .toString());
518                        }
519                        catch (Exception e)
520                        {
521
522                        }
523                     }
524                  }
525               }
526
527               user.setField(attrID, attrs.get(attrID)
528                                          .get()
529                                          .toString());
530            }
531         }
532
533         // Examine the paged results control response
534         Control[] controls = inCtx.getResponseControls();
535         if (controls != null)
536         {
537            for (int i = 0; i < controls.length; i++)
538            {
539               if (controls[i] instanceof PagedResultsResponseControl)
540               {
541                  PagedResultsResponseControl prrc =
542                        (PagedResultsResponseControl) controls[i];
543                  total = prrc.getResultSize();
544                  cookie = prrc.getCookie();
545               }
546               else
547               {
548                  // Handle other response controls (if any)
549               }
550            }
551         }
552
553         // Re-activate paged results
554         inCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize, cookie, Control.CRITICAL)});
555
556      } while (cookie != null);
557
558      return users;
559   }
560
561   //---------------------------------------------------------------------------
562   private List<U> getInfoForUsersWithoutPaging(LdapContext inCtx, LdapName inBaseDN, String inFilter, List<LDAP_UserField> inFields)
563   {
564      List<U> users = null;
565
566      LdapName baseDN = inBaseDN;
567      if (null == baseDN)
568      {
569         baseDN = mLDAP_UserContext; // Use the default user context
570      }
571
572      try
573      {
574         // get more attributes about this user
575         SearchControls searchControls = new SearchControls();
576         searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
577         
578         Map<String, LDAP_UserField> fieldMap = null;
579
580         String[] attributesToRetrieve = new String[] {"*"}; // Default to all attributes
581         if (CollectionUtil.hasValues(inFields))
582         {
583            fieldMap = new HashMap<>(inFields.size());
584            List<String> attrNames = new ArrayList<>(inFields.size());
585            for (LDAP_UserField field : inFields)
586            {
587               attrNames.add(field.name());
588               fieldMap.put(field.name(), field);
589            }
590
591            attributesToRetrieve = attrNames.toArray(new String[] {});
592         }
593
594         searchControls.setReturningAttributes(attributesToRetrieve);
595         LOGGER.fine("LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(baseDN));
596
597         NamingEnumeration enumeration = inCtx.search(baseDN, inFilter, searchControls);
598         while (enumeration.hasMore())
599         {
600            if (null == users)
601            {
602               users = new ArrayList<>();
603            }
604
605            U user = getUserFactory().createUserObj();
606            users.add(user);
607
608            Attributes attrs = ((SearchResult) enumeration.next()).getAttributes();
609            NamingEnumeration<String> attrEnum = attrs.getIDs();
610            while (attrEnum.hasMoreElements())
611            {
612               String attrID = attrEnum.nextElement();
613               if (fieldMap != null)
614               {
615                  LDAP_UserField field = fieldMap.get(attrID);
616                  if (field != null)
617                  {
618                     Method setter = field.getUserObjectSetter();
619                     if (setter != null)
620                     {
621                        try
622                        {
623                           setter.invoke(user, attrs.get(attrID).get().toString());
624                        }
625                        catch (Exception e)
626                        {
627
628                        }
629                     }
630                  }
631               }
632
633               user.setField(attrID, attrs.get(attrID).get().toString());
634            }
635         }
636      }
637      catch (Exception e)
638      {
639         throw composeException(e);
640      }
641      finally
642      {
643         if (inCtx != null)
644         {
645            try
646            {
647               inCtx.close();
648            }
649            catch (NamingException e)
650            {
651
652            }
653         }
654      }
655
656      return users;
657   }
658
659   //---------------------------------------------------------------------------
660   public List<U> getUsersForGroup(String inFilter)
661   {
662      List<U> users;
663
664      LdapContext ctx = null;
665      try
666      {
667         // Create the initial context
668         ctx = getInitialDirContext(mLDAP_PrincipalCredentials);
669
670         if (isPagingSupported(ctx))
671         {
672            users = getUsersForGroupWithPaging(ctx, inFilter);   
673         }
674         else
675         {
676            users = getUsersForGroupWithoutPaging(ctx, inFilter);
677         }
678      }
679      catch (Exception e)
680      {
681         throw composeException(e);
682      }
683      finally
684      {
685         if (ctx != null)
686         {
687            try
688            {
689               ctx.close();
690            }
691            catch (NamingException e)
692            {
693
694            }
695         }
696      }
697
698      int numUsersFound = (CollectionUtil.hasValues(users) ? users.size() : 0);
699      LOGGER.fine(numUsersFound + " user" + (numUsersFound != 1 ? "s" : "") + " found by LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
700
701      return users;
702   }
703   
704   //---------------------------------------------------------------------------
705   private List<U> getUsersForGroupWithPaging(LdapContext inCtx, String inFilter)
706      throws Exception
707   {
708      List<U> users = null;
709
710      // get more attributes about this user
711      SearchControls searchControls = new SearchControls();
712      searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
713
714      String[] attributesToRetrieve = new String[] {"*"}; // Default to all attributes
715
716      searchControls.setReturningAttributes(attributesToRetrieve);
717      LOGGER.fine("LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
718
719      // Activate paged results
720      int pageSize = 20; // 20 entries per page
721      byte[] cookie = null;
722      int total;
723      inCtx.setRequestControls(new Control[]{
724            new PagedResultsControl(pageSize, Control.CRITICAL) });
725      do
726      {
727         // Perform the search
728         NamingEnumeration results = inCtx.search(mLDAP_GroupContext, inFilter, searchControls);
729         while (results != null
730               && results.hasMore())
731         {
732            Attributes                attrs    = ((SearchResult) results.next()).getAttributes();
733            NamingEnumeration<String> attrEnum = attrs.getIDs();
734            while (attrEnum.hasMoreElements())
735            {
736               String attrID = attrEnum.nextElement();
737               if (attrID.equalsIgnoreCase("member")
738                     || attrID.equalsIgnoreCase("uniquemember"))
739               {
740                  Attribute attr = attrs.get(attrID);
741                  users = new ArrayList<>(attr.size());
742                  NamingEnumeration<String> memberEnum = (NamingEnumeration<String>) attr.getAll();
743                  while (memberEnum.hasMoreElements())
744                  {
745                     LdapName userDN = new LdapName(memberEnum.nextElement());
746
747                     users.add(getInfoForUser(userDN, userDN.remove(userDN.size() - 1)
748                                                            .toString()));
749                  }
750
751               }
752            }
753         }
754
755         // Examine the paged results control response
756         Control[] controls = inCtx.getResponseControls();
757         if (controls != null)
758         {
759            for (int i = 0; i < controls.length; i++)
760            {
761               if (controls[i] instanceof PagedResultsResponseControl)
762               {
763                  PagedResultsResponseControl prrc =
764                        (PagedResultsResponseControl) controls[i];
765                  total = prrc.getResultSize();
766                  cookie = prrc.getCookie();
767               }
768               else
769               {
770                  // Handle other response controls (if any)
771               }
772            }
773         }
774
775         // Re-activate paged results
776         inCtx.setRequestControls(new Control[]{ new PagedResultsControl(pageSize, cookie, Control.CRITICAL) });
777
778      } while (cookie != null);
779
780      return users;
781   }
782
783   //---------------------------------------------------------------------------
784   private List<U> getUsersForGroupWithoutPaging(LdapContext inCtx, String inFilter)
785      throws Exception
786   {
787      List<U> users = null;
788
789      // get more attributes about this user
790      SearchControls searchControls = new SearchControls();
791      searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
792
793      String[] attributesToRetrieve = new String[] {"*"}; // Default to all attributes
794
795      searchControls.setReturningAttributes(attributesToRetrieve);
796      LOGGER.fine("LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
797
798      // Perform the search
799      NamingEnumeration results = inCtx.search(mLDAP_GroupContext, inFilter, searchControls);
800      while (results != null
801            && results.hasMore())
802      {
803         Attributes attrs = ((SearchResult) results.next()).getAttributes();
804         NamingEnumeration<String> attrEnum = attrs.getIDs();
805         while (attrEnum.hasMoreElements())
806         {
807            String attrID = attrEnum.nextElement();
808            if (attrID.equalsIgnoreCase("member")
809                  || attrID.equalsIgnoreCase("uniquemember"))
810            {
811               Attribute attr = attrs.get(attrID);
812               users = new ArrayList<>(attr.size());
813               NamingEnumeration<String> memberEnum = (NamingEnumeration<String>) attr.getAll();
814               while (memberEnum.hasMoreElements())
815               {
816                  LdapName userDN = new LdapName(memberEnum.nextElement());
817
818                  users.add(getInfoForUser(userDN, userDN.remove(userDN.size() - 1).toString()));
819               }
820            }
821         }
822      }
823
824      return users;
825   }
826
827 
828
829
830   //###########################################################################
831   // PRIVATE METHODS
832   //###########################################################################
833
834   //---------------------------------------------------------------------------
835   /**
836    Checks to run before an operation.
837    */
838   private void crosscheck()
839   {
840      if (null == mLDAP_ServerURL)
841      {
842         throw new LDAP_Exception("No LDAP server has been specified!");
843      }
844   }
845
846
847   //---------------------------------------------------------------------------
848   private LdapContext getInitialDirContext(LoginCredentials inCredentials)
849      throws Exception
850   {
851      crosscheck();
852
853
854      // creating environment for initial context
855      Hashtable<String, String> env = new Hashtable<>();
856      env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
857
858      if (mReferralHandling != null)
859      {
860         // Set how referrals should be handled
861         env.put(Context.REFERRAL, mReferralHandling.getValue());
862      }
863
864      LOGGER.info("Setting provider URL to " + StringUtil.singleQuote(mLDAP_ServerURL));
865      env.put(Context.PROVIDER_URL, mLDAP_ServerURL);
866//      env.put(Context.SECURITY_AUTHENTICATION, "simple");
867
868      if (null == inCredentials)
869      {
870         throw new LDAP_Exception("No credentials specified!");
871      }
872
873      String securityPrincipal = inCredentials.getUser(); // Active Directory may not want more than this
874      if (mLDAP_PrincipalContext != null)
875      {
876         securityPrincipal = mLDAP_PrincipalFieldName + "=" + inCredentials.getUser() + (mLDAP_PrincipalContext != null ? "," + mLDAP_PrincipalContext : "");
877      }
878      else if (StringUtil.isSet(mLDAP_PrincipalSuffix)
879               && ! securityPrincipal.endsWith(mLDAP_PrincipalSuffix))
880      {
881         securityPrincipal += mLDAP_PrincipalSuffix;
882      }
883
884      LOGGER.info("Setting security principal to " + StringUtil.singleQuote(securityPrincipal));
885      env.put(Context.SECURITY_PRINCIPAL, securityPrincipal);
886
887      env.put(Context.SECURITY_CREDENTIALS, inCredentials.getPasswordString());
888
889      LOGGER.info("Establishing LDAP connection...");
890
891      Control[] controls = null;
892      
893      // Create the initial context
894      return new InitialLdapContext(env, controls);
895   }
896
897   //---------------------------------------------------------------------------
898   private boolean isPagingSupported(DirContext inCtx)
899         throws NamingException
900   {
901      return getSupportedControls(inCtx).contains(PAGING_OID);
902   }
903
904   //---------------------------------------------------------------------------
905   private Set<String> getSupportedControls(DirContext inCtx)
906         throws NamingException
907   {
908      if (null == mSupportedControls)
909      {
910         Hashtable<?, ?> environment = inCtx.getEnvironment();
911         URI uri = URI.create("" + environment.get("java.naming.provider.url"));
912         final String rootDse = uri.getScheme() + "://" + uri.getAuthority();
913
914         mSupportedControls = new HashSet<>(10);
915
916         final Attributes attributes = inCtx.getAttributes(rootDse, new String[]{"supportedControl"});
917
918         final NamingEnumeration attributeValues = attributes.getAll();
919         while (attributeValues.hasMore())
920         {
921            final Attribute attribute = (Attribute) attributeValues.next();
922            final NamingEnumeration supportedControls = attribute.getAll();
923            while (supportedControls.hasMore())
924            {
925               mSupportedControls.add((String) supportedControls.next());
926            }
927         }
928      }
929
930      return mSupportedControls;
931   }
932
933   private LDAP_Exception composeException(Exception inException)
934   {
935      StringBuilderPlus msg = new StringBuilderPlus("Problem querying " + mLDAP_ServerURL + " !");
936
937      // If the problem was with a certificate, try to get a summary of the cert.
938      Throwable rootException = StackTraceUtil.getRootException(inException);
939      if (rootException != null
940            && rootException instanceof SSLHandshakeException)
941      {
942         try
943         {
944            Set<X509Certificate> certs = CertificateUtil.geLdapCertificates(mLDAP_ServerURL);
945            if (CollectionUtil.hasValues(certs))
946            {
947               msg.appendln();
948               msg.appendln(CertificateUtil.generateCertSummary(certs.iterator().next()));
949            }
950         }
951         catch (Exception e2)
952         {
953            // Ignore
954         }
955      }
956
957      return new LDAP_Exception(msg.toString(), inException);
958   }
959}