com.hfg.ldap.LDAPClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.ldap;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl;
import javax.net.ssl.SSLHandshakeException;
import com.hfg.cert.CertificateUtil;
import com.hfg.exception.InvalidValueException;
import com.hfg.html.Col;
import com.hfg.ldap.ad.ActiveDirectory;
import com.hfg.security.LoginCredentials;
import com.hfg.util.StackTraceUtil;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import static com.hfg.cert.CertificateUtil.geLdapCertificates;
//------------------------------------------------------------------------------
/**
Simple LDAP Client.
@author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
public class LDAPClient
{
private static final ReferralHandling sDefaultReferralHandling = ReferralHandling.FOLLOW_REFERRALS;
private static final Logger LOGGER = Logger.getLogger(LDAPClient.class.getPackage().getName());
private String mLDAP_ServerURL;
private LdapName mLDAP_UserContext;
private LdapName mLDAP_GroupContext;
private String mLDAP_PrincipalFieldName = "uid";
private LdapName mLDAP_PrincipalContext;
private String mLDAP_PrincipalSuffix;
private LoginCredentials mLDAP_PrincipalCredentials;
private LDAP_UserObjFactory mUserFactory = new LDAP_UserFactory();
private ReferralHandling mReferralHandling = sDefaultReferralHandling;
private Set mSupportedControls;
private static final String PAGING_OID = "1.2.840.113556.1.4.319";
private static final String VLV_OID = "2.16.840.1.113730.3.4.9";
public enum ReferralHandling
{
FOLLOW_REFERRALS("follow"),
IGNORE_REFERRALS("ignore"),
THROW_REFERRALS("throw");
private String mValue;
private ReferralHandling(String inValue)
{
mValue = inValue;
}
public String getValue()
{
return mValue;
}
@Override
public String toString()
{
return getValue();
}
}
static
{
LOGGER.setLevel(Level.WARNING);
LOGGER.setUseParentHandlers(true);
}
//###########################################################################
// CONSTRUCTORS
//###########################################################################
//------------------------------------------------------------------------
public LDAPClient()
{
}
//------------------------------------------------------------------------
public LDAPClient(LDAP_Config inConfig)
{
this();
setServerURL(inConfig.getServerURL());
setUserContext(inConfig.getUserContext());
setPrincipalFieldName(inConfig.getPrincipalFieldName());
setPrincipalCredentials(inConfig.getPrincipalCredentials());
setReferralHandling(inConfig.getReferralHandling());
}
//###########################################################################
// PUBLIC METHODS
//###########################################################################
//---------------------------------------------------------------------------
public static Logger getLogger()
{
return LOGGER;
}
//---------------------------------------------------------------------------
public LDAPClient setServerURL(String inValue)
{
mLDAP_ServerURL = inValue;
return this;
}
//---------------------------------------------------------------------------
public String getServerURL()
{
return mLDAP_ServerURL;
}
//---------------------------------------------------------------------------
public LDAPClient setUserContext(String inValue)
{
try
{
setUserContext(inValue != null ? new LdapName(inValue) : null);
}
catch (InvalidNameException e)
{
throw new RuntimeException(e);
}
return this;
}
//---------------------------------------------------------------------------
public LDAPClient setReferralHandling(ReferralHandling inValue)
{
mReferralHandling = inValue;
return this;
}
//---------------------------------------------------------------------------
public ReferralHandling getReferralHandling()
{
return mReferralHandling;
}
//---------------------------------------------------------------------------
public LDAPClient setUserContext(LdapName inValue)
{
mLDAP_UserContext = inValue;
return this;
}
//---------------------------------------------------------------------------
public LDAPClient setGroupContext(String inValue)
{
try
{
setGroupContext(inValue != null ? new LdapName(inValue) : null);
}
catch (InvalidNameException e)
{
throw new RuntimeException(e);
}
return this;
}
//---------------------------------------------------------------------------
public LDAPClient setGroupContext(LdapName inValue)
{
mLDAP_GroupContext = inValue;
return this;
}
//---------------------------------------------------------------------------
public LDAPClient setPrincipalContext(String inValue)
{
try
{
setPrincipalContext(inValue != null ? new LdapName(inValue) : null);
}
catch (InvalidNameException e)
{
throw new RuntimeException(e);
}
return this;
}
//---------------------------------------------------------------------------
public LDAPClient setPrincipalContext(LdapName inValue)
{
mLDAP_PrincipalContext = inValue;
return this;
}
//---------------------------------------------------------------------------
public LDAPClient setPrincipalSuffix(String inValue)
{
mLDAP_PrincipalSuffix = inValue;
return this;
}
//---------------------------------------------------------------------------
/**
Specifies the credentials to be used to access the LDAP server.
* @param inValue the security principal credentials
* @return this LDAPClient object to facilitate method chaining
*/
public LDAPClient setPrincipalCredentials(LoginCredentials inValue)
{
mLDAP_PrincipalCredentials = inValue;
return this;
}
//---------------------------------------------------------------------------
public LDAPClient setPrincipalFieldName(String inValue)
{
mLDAP_PrincipalFieldName = inValue;
return this;
}
//---------------------------------------------------------------------------
public LDAP_UserObjFactory getUserFactory()
{
return mUserFactory;
}
//---------------------------------------------------------------------------
public LDAPClient setUserFactory(LDAP_UserObjFactory inValue)
{
if (null == inValue)
{
throw new InvalidValueException("The user factory cannot be set to null!");
}
mUserFactory = inValue;
return this;
}
//---------------------------------------------------------------------------
// TODO: Should this method return a more complex result that could contain more info about failures?
public boolean authenticate(LoginCredentials inCredentials)
{
crosscheck();
boolean authenticated = false;
DirContext ctx = null;
try
{
// Create the initial context
ctx = getInitialDirContext(inCredentials);
if (ctx != null)
{
authenticated = true;
}
/*
// get more attributes about this user
SearchControls scs = new SearchControls();
scs.setSearchScope(SearchControls.SUBTREE_SCOPE);
String[] attrNames = { "mail", "cn" };
scs.setReturningAttributes(attrNames);
NamingEnumeration nes = ctx.search(mLDAP_UserContext, "uid=" + userName, scs);
if(nes.hasMore())
{
Attributes attrs = ((SearchResult) nes.next()).getAttributes();
System.out.println("mail: " + attrs.get("mail").get());
System.out.println("cn: " + attrs.get("cn").get());
}
*/
}
catch (AuthenticationException e)
{
// If it looks like an Active Directory exception, try to decode it...
String msg = ActiveDirectory.decodeAuthenticationException(e);
if (StringUtil.isSet(msg))
{
new LDAP_Exception(msg, e).printStackTrace();
}
else
{
e.printStackTrace();
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
if (ctx != null)
{
try
{
ctx.close();
}
catch (NamingException e)
{
// Ignore
}
}
}
return authenticated;
}
//---------------------------------------------------------------------------
public U getInfoForUser(String inQuery)
{
return getInfoForUser(inQuery, null);
}
//---------------------------------------------------------------------------
public U getInfoForUser(String inQuery, List inFields)
{
return getInfoForUser(null, inQuery, inFields);
}
//---------------------------------------------------------------------------
public U getInfoForUser(LdapName inBaseDN, String inQuery)
{
return getInfoForUser(inBaseDN, inQuery, null);
}
//---------------------------------------------------------------------------
public U getInfoForUser(LdapName inBaseDN, String inQuery, List inFields)
{
List users = getInfoForUsers(inBaseDN, inQuery, inFields);
return (CollectionUtil.hasValues(users) ? users.get(0) : null);
}
//---------------------------------------------------------------------------
public List getInfoForUsers(String inQuery)
{
return getInfoForUsers(inQuery, null);
}
//---------------------------------------------------------------------------
public List getInfoForUsers(String inFilter, List inFields)
{
return getInfoForUsers(null, inFilter, inFields);
}
//---------------------------------------------------------------------------
public List getInfoForUsers(LdapName inBaseDN, String inFilter, List inFields)
{
List users;
LdapContext ctx = null;
try
{
// Create the initial context
ctx = getInitialDirContext(mLDAP_PrincipalCredentials);
if (isPagingSupported(ctx))
{
users = getInfoForUsersWithPaging(ctx, inBaseDN, inFilter, inFields);
}
else
{
users = getInfoForUsersWithoutPaging(ctx, inBaseDN, inFilter, inFields);
}
}
catch (Exception e)
{
throw composeException(e);
}
finally
{
if (ctx != null)
{
try
{
ctx.close();
}
catch (NamingException e)
{
}
}
}
int numUsersFound = (CollectionUtil.hasValues(users) ? users.size() : 0);
LOGGER.fine(numUsersFound + " user" + (numUsersFound != 1 ? "s" : "") + " found by LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
return users;
}
//---------------------------------------------------------------------------
public List getInfoForUsersWithPaging(LdapContext inCtx, LdapName inBaseDN, String inFilter, List inFields)
throws Exception
{
List users = null;
LdapName baseDN = inBaseDN;
if (null == baseDN)
{
baseDN = mLDAP_UserContext; // Use the default user context
}
Map fieldMap = null;
// get more attributes about this user
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String[] attributesToRetrieve = new String[] {"*"}; // Default to all attributes
if (CollectionUtil.hasValues(inFields))
{
fieldMap = new HashMap<>(inFields.size());
List attrNames = new ArrayList<>(inFields.size());
for (LDAP_UserField field : inFields)
{
attrNames.add(field.name());
fieldMap.put(field.name(), field);
}
attributesToRetrieve = attrNames.toArray(new String[] {});
}
searchControls.setReturningAttributes(attributesToRetrieve);
LOGGER.fine("LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
// Activate paged results
int pageSize = 1000; // 1000 entries per page
byte[] cookie = null;
int total;
inCtx.setRequestControls(new Control[]{
new PagedResultsControl(pageSize, Control.CRITICAL) });
do
{
// Perform the search
NamingEnumeration results = inCtx.search(baseDN, inFilter, searchControls);
while (results != null
&& results.hasMore())
{
if (null == users)
{
users = new ArrayList<>();
}
U user = getUserFactory().createUserObj();
users.add(user);
Attributes attrs = ((SearchResult) results.next()).getAttributes();
NamingEnumeration attrEnum = attrs.getIDs();
while (attrEnum.hasMoreElements())
{
String attrID = attrEnum.nextElement();
if (fieldMap != null)
{
LDAP_UserField field = fieldMap.get(attrID);
if (field != null)
{
Method setter = field.getUserObjectSetter();
if (setter != null)
{
try
{
setter.invoke(user, attrs.get(attrID)
.get()
.toString());
}
catch (Exception e)
{
}
}
}
}
user.setField(attrID, attrs.get(attrID)
.get()
.toString());
}
}
// Examine the paged results control response
Control[] controls = inCtx.getResponseControls();
if (controls != null)
{
for (int i = 0; i < controls.length; i++)
{
if (controls[i] instanceof PagedResultsResponseControl)
{
PagedResultsResponseControl prrc =
(PagedResultsResponseControl) controls[i];
total = prrc.getResultSize();
cookie = prrc.getCookie();
}
else
{
// Handle other response controls (if any)
}
}
}
// Re-activate paged results
inCtx.setRequestControls(new Control[]{new PagedResultsControl(pageSize, cookie, Control.CRITICAL)});
} while (cookie != null);
return users;
}
//---------------------------------------------------------------------------
private List getInfoForUsersWithoutPaging(LdapContext inCtx, LdapName inBaseDN, String inFilter, List inFields)
{
List users = null;
LdapName baseDN = inBaseDN;
if (null == baseDN)
{
baseDN = mLDAP_UserContext; // Use the default user context
}
try
{
// get more attributes about this user
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
Map fieldMap = null;
String[] attributesToRetrieve = new String[] {"*"}; // Default to all attributes
if (CollectionUtil.hasValues(inFields))
{
fieldMap = new HashMap<>(inFields.size());
List attrNames = new ArrayList<>(inFields.size());
for (LDAP_UserField field : inFields)
{
attrNames.add(field.name());
fieldMap.put(field.name(), field);
}
attributesToRetrieve = attrNames.toArray(new String[] {});
}
searchControls.setReturningAttributes(attributesToRetrieve);
LOGGER.fine("LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(baseDN));
NamingEnumeration enumeration = inCtx.search(baseDN, inFilter, searchControls);
while (enumeration.hasMore())
{
if (null == users)
{
users = new ArrayList<>();
}
U user = getUserFactory().createUserObj();
users.add(user);
Attributes attrs = ((SearchResult) enumeration.next()).getAttributes();
NamingEnumeration attrEnum = attrs.getIDs();
while (attrEnum.hasMoreElements())
{
String attrID = attrEnum.nextElement();
if (fieldMap != null)
{
LDAP_UserField field = fieldMap.get(attrID);
if (field != null)
{
Method setter = field.getUserObjectSetter();
if (setter != null)
{
try
{
setter.invoke(user, attrs.get(attrID).get().toString());
}
catch (Exception e)
{
}
}
}
}
user.setField(attrID, attrs.get(attrID).get().toString());
}
}
}
catch (Exception e)
{
throw composeException(e);
}
finally
{
if (inCtx != null)
{
try
{
inCtx.close();
}
catch (NamingException e)
{
}
}
}
return users;
}
//---------------------------------------------------------------------------
public List getUsersForGroup(String inFilter)
{
List users;
LdapContext ctx = null;
try
{
// Create the initial context
ctx = getInitialDirContext(mLDAP_PrincipalCredentials);
if (isPagingSupported(ctx))
{
users = getUsersForGroupWithPaging(ctx, inFilter);
}
else
{
users = getUsersForGroupWithoutPaging(ctx, inFilter);
}
}
catch (Exception e)
{
throw composeException(e);
}
finally
{
if (ctx != null)
{
try
{
ctx.close();
}
catch (NamingException e)
{
}
}
}
int numUsersFound = (CollectionUtil.hasValues(users) ? users.size() : 0);
LOGGER.fine(numUsersFound + " user" + (numUsersFound != 1 ? "s" : "") + " found by LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
return users;
}
//---------------------------------------------------------------------------
private List getUsersForGroupWithPaging(LdapContext inCtx, String inFilter)
throws Exception
{
List users = null;
// get more attributes about this user
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String[] attributesToRetrieve = new String[] {"*"}; // Default to all attributes
searchControls.setReturningAttributes(attributesToRetrieve);
LOGGER.fine("LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
// Activate paged results
int pageSize = 20; // 20 entries per page
byte[] cookie = null;
int total;
inCtx.setRequestControls(new Control[]{
new PagedResultsControl(pageSize, Control.CRITICAL) });
do
{
// Perform the search
NamingEnumeration results = inCtx.search(mLDAP_GroupContext, inFilter, searchControls);
while (results != null
&& results.hasMore())
{
Attributes attrs = ((SearchResult) results.next()).getAttributes();
NamingEnumeration attrEnum = attrs.getIDs();
while (attrEnum.hasMoreElements())
{
String attrID = attrEnum.nextElement();
if (attrID.equalsIgnoreCase("member")
|| attrID.equalsIgnoreCase("uniquemember"))
{
Attribute attr = attrs.get(attrID);
users = new ArrayList<>(attr.size());
NamingEnumeration memberEnum = (NamingEnumeration) attr.getAll();
while (memberEnum.hasMoreElements())
{
LdapName userDN = new LdapName(memberEnum.nextElement());
users.add(getInfoForUser(userDN, userDN.remove(userDN.size() - 1)
.toString()));
}
}
}
}
// Examine the paged results control response
Control[] controls = inCtx.getResponseControls();
if (controls != null)
{
for (int i = 0; i < controls.length; i++)
{
if (controls[i] instanceof PagedResultsResponseControl)
{
PagedResultsResponseControl prrc =
(PagedResultsResponseControl) controls[i];
total = prrc.getResultSize();
cookie = prrc.getCookie();
}
else
{
// Handle other response controls (if any)
}
}
}
// Re-activate paged results
inCtx.setRequestControls(new Control[]{ new PagedResultsControl(pageSize, cookie, Control.CRITICAL) });
} while (cookie != null);
return users;
}
//---------------------------------------------------------------------------
private List getUsersForGroupWithoutPaging(LdapContext inCtx, String inFilter)
throws Exception
{
List users = null;
// get more attributes about this user
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String[] attributesToRetrieve = new String[] {"*"}; // Default to all attributes
searchControls.setReturningAttributes(attributesToRetrieve);
LOGGER.fine("LDAP Query " + StringUtil.singleQuote(inFilter) + " in context " + StringUtil.singleQuote(mLDAP_GroupContext));
// Perform the search
NamingEnumeration results = inCtx.search(mLDAP_GroupContext, inFilter, searchControls);
while (results != null
&& results.hasMore())
{
Attributes attrs = ((SearchResult) results.next()).getAttributes();
NamingEnumeration attrEnum = attrs.getIDs();
while (attrEnum.hasMoreElements())
{
String attrID = attrEnum.nextElement();
if (attrID.equalsIgnoreCase("member")
|| attrID.equalsIgnoreCase("uniquemember"))
{
Attribute attr = attrs.get(attrID);
users = new ArrayList<>(attr.size());
NamingEnumeration memberEnum = (NamingEnumeration) attr.getAll();
while (memberEnum.hasMoreElements())
{
LdapName userDN = new LdapName(memberEnum.nextElement());
users.add(getInfoForUser(userDN, userDN.remove(userDN.size() - 1).toString()));
}
}
}
}
return users;
}
//###########################################################################
// PRIVATE METHODS
//###########################################################################
//---------------------------------------------------------------------------
/**
Checks to run before an operation.
*/
private void crosscheck()
{
if (null == mLDAP_ServerURL)
{
throw new LDAP_Exception("No LDAP server has been specified!");
}
}
//---------------------------------------------------------------------------
private LdapContext getInitialDirContext(LoginCredentials inCredentials)
throws Exception
{
crosscheck();
// creating environment for initial context
Hashtable env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
if (mReferralHandling != null)
{
// Set how referrals should be handled
env.put(Context.REFERRAL, mReferralHandling.getValue());
}
LOGGER.info("Setting provider URL to " + StringUtil.singleQuote(mLDAP_ServerURL));
env.put(Context.PROVIDER_URL, mLDAP_ServerURL);
// env.put(Context.SECURITY_AUTHENTICATION, "simple");
if (null == inCredentials)
{
throw new LDAP_Exception("No credentials specified!");
}
String securityPrincipal = inCredentials.getUser(); // Active Directory may not want more than this
if (mLDAP_PrincipalContext != null)
{
securityPrincipal = mLDAP_PrincipalFieldName + "=" + inCredentials.getUser() + (mLDAP_PrincipalContext != null ? "," + mLDAP_PrincipalContext : "");
}
else if (StringUtil.isSet(mLDAP_PrincipalSuffix)
&& ! securityPrincipal.endsWith(mLDAP_PrincipalSuffix))
{
securityPrincipal += mLDAP_PrincipalSuffix;
}
LOGGER.info("Setting security principal to " + StringUtil.singleQuote(securityPrincipal));
env.put(Context.SECURITY_PRINCIPAL, securityPrincipal);
env.put(Context.SECURITY_CREDENTIALS, inCredentials.getPasswordString());
LOGGER.info("Establishing LDAP connection...");
Control[] controls = null;
// Create the initial context
return new InitialLdapContext(env, controls);
}
//---------------------------------------------------------------------------
private boolean isPagingSupported(DirContext inCtx)
throws NamingException
{
return getSupportedControls(inCtx).contains(PAGING_OID);
}
//---------------------------------------------------------------------------
private Set getSupportedControls(DirContext inCtx)
throws NamingException
{
if (null == mSupportedControls)
{
Hashtable, ?> environment = inCtx.getEnvironment();
URI uri = URI.create("" + environment.get("java.naming.provider.url"));
final String rootDse = uri.getScheme() + "://" + uri.getAuthority();
mSupportedControls = new HashSet<>(10);
final Attributes attributes = inCtx.getAttributes(rootDse, new String[]{"supportedControl"});
final NamingEnumeration attributeValues = attributes.getAll();
while (attributeValues.hasMore())
{
final Attribute attribute = (Attribute) attributeValues.next();
final NamingEnumeration supportedControls = attribute.getAll();
while (supportedControls.hasMore())
{
mSupportedControls.add((String) supportedControls.next());
}
}
}
return mSupportedControls;
}
private LDAP_Exception composeException(Exception inException)
{
StringBuilderPlus msg = new StringBuilderPlus("Problem querying " + mLDAP_ServerURL + " !");
// If the problem was with a certificate, try to get a summary of the cert.
Throwable rootException = StackTraceUtil.getRootException(inException);
if (rootException != null
&& rootException instanceof SSLHandshakeException)
{
try
{
Set certs = CertificateUtil.geLdapCertificates(mLDAP_ServerURL);
if (CollectionUtil.hasValues(certs))
{
msg.appendln();
msg.appendln(CertificateUtil.generateCertSummary(certs.iterator().next()));
}
}
catch (Exception e2)
{
// Ignore
}
}
return new LDAP_Exception(msg.toString(), inException);
}
}