Java tutorial
/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.core.user.ldap; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Set; import javax.naming.AuthenticationException; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.SizeLimitExceededException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import org.apache.commons.lang.StringUtils; import org.eclipse.skalli.commons.CollectionUtils; import org.eclipse.skalli.core.user.NormalizeUtil; import org.eclipse.skalli.model.User; import org.osgi.framework.FrameworkUtil; import org.osgi.service.jndi.JNDIConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LDAPClient { private final static Logger LOG = LoggerFactory.getLogger(LDAPClient.class); private static final String LDAPS_SCHEME = "ldaps:"; //$NON-NLS-1$ private static final String SIMPLE_AUTHENTICATION = "simple"; //$NON-NLS-1$ private static final String JNDI_SOCKET_FACTORY = "java.naming.ldap.factory.socket"; //$NON-NLS-1$ private static final String DEFAULT_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; //$NON-NLS-1$ private static final String READ_TIMEOUT = "com.sun.jndi.ldap.read.timeout";//$NON-NLS-1$ private static final String USE_CONNECTION_POOLING = "com.sun.jndi.ldap.connect.pool"; //$NON-NLS-1$ private static final String CONNECT_POOL_PROTOCOLS = "com.sun.jndi.ldap.connect.pool.protocol"; //$NON-NLS-1$ private static final String CONNECT_POOL_DEBUG = "com.sun.jndi.ldap.connect.pool.debug"; //$NON-NLS-1$ private static final String CONNECT_POOL_MAXSIZE = "com.sun.jndi.ldap.connect.pool.maxsize"; //$NON-NLS-1$ private static final String CONNECT_POOL_TIMEOUT = "com.sun.jndi.ldap.connect.pool.timeout"; //$NON-NLS-1$ private static final String DEFAULT_READ_TIMEOUT = "30000"; //$NON-NLS-1$ private static final String DEFAULT_CONNECT_TIMEOUT = "10000"; //$NON-NLS-1$ private static final String DEFAULT_POOL_MAXSIZE = "5"; //$NON-NLS-1$ private static final String DEFAULT_POOL_PROTOCOLS = "plain ssl"; //$NON-NLS-1$ static { // heaven knows why the following params are system properties while all other params can be set per context... System.setProperty(CONNECT_POOL_PROTOCOLS, DEFAULT_POOL_PROTOCOLS); System.setProperty(CONNECT_POOL_MAXSIZE, DEFAULT_POOL_MAXSIZE); System.setProperty(CONNECT_POOL_TIMEOUT, DEFAULT_CONNECT_TIMEOUT); if (LOG.isDebugEnabled()) { System.setProperty(CONNECT_POOL_DEBUG, "fine"); //$NON-NLS-1$ } } private LDAPConfig config; public LDAPClient(LDAPConfig config) { this.config = config; } private LdapContext getLdapContext() throws NamingException, AuthenticationException { if (config == null) { throw new NamingException("LDAP not configured"); } if (StringUtils.isBlank(config.getProviderUrl())) { throw new NamingException("No LDAP server available"); } if (StringUtils.isBlank(config.getUsername()) || StringUtils.isBlank(config.getPassword())) { throw new AuthenticationException("No LDAP credentials available"); } String ctxFactory = config.getCtxFactory(); if (StringUtils.isBlank(ctxFactory)) { ctxFactory = DEFAULT_CONTEXT_FACTORY; } String authentication = config.getAuthentication(); if (StringUtils.isBlank(authentication)) { authentication = SIMPLE_AUTHENTICATION; } Hashtable<String, Object> env = new Hashtable<String, Object>(); env.put(Context.INITIAL_CONTEXT_FACTORY, ctxFactory); env.put(Context.PROVIDER_URL, config.getProviderUrl()); env.put(Context.SECURITY_PRINCIPAL, config.getUsername()); env.put(Context.SECURITY_CREDENTIALS, config.getPassword()); env.put(Context.SECURITY_AUTHENTICATION, authentication); if (StringUtils.isNotBlank(config.getReferral())) { env.put(Context.REFERRAL, config.getReferral()); } if (config.getProviderUrl().startsWith(LDAPS_SCHEME)) { env.put(Context.SECURITY_PROTOCOL, "ssl"); //$NON-NLS-1$ if (config.isSslNoVerify()) { env.put(JNDI_SOCKET_FACTORY, LDAPTrustAllSocketFactory.class.getName()); } } // Gemini-specific properties env.put(JNDIConstants.BUNDLE_CONTEXT, FrameworkUtil.getBundle(LDAPClient.class).getBundleContext()); // com.sun.jndi.ldap.LdapCtxFactory specific properties env.put(READ_TIMEOUT, DEFAULT_READ_TIMEOUT); env.put(USE_CONNECTION_POOLING, "true"); //$NON-NLS-1$ // extremly ugly classloading workaround: // com.sun.jndi.ldap.LdapCtxFactory uses Class.forName() to load the socket factory, shame on them! InitialLdapContext ctx = null; ClassLoader classloader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(LDAPTrustAllSocketFactory.class.getClassLoader()); ctx = new InitialLdapContext(env, null); } finally { if (classloader != null) { Thread.currentThread().setContextClassLoader(classloader); } } return ctx; } private void closeQuietly(LdapContext ldap) { if (ldap != null) { try { ldap.close(); } catch (NamingException e) { LOG.error("Failed to close LDAP connection", e); } } } private void closeQuietly(NamingEnumeration<?> result) { if (result != null) { try { result.close(); } catch (NamingException e) { LOG.error("Failed to close LDAP result set", e); } } } public User searchUserById(String userId) { LdapContext ldap = null; try { ldap = getLdapContext(); return searchUserById(ldap, userId); } catch (Exception e) { LOG.debug(MessageFormat.format("Failed to retrieve user ''{0}''", userId), e); return new User(userId); } finally { closeQuietly(ldap); } } public Set<User> searchUsersByIds(Set<String> userIds) { LdapContext ldap = null; try { Set<User> ret = new HashSet<User>(); ldap = getLdapContext(); for (String userId : userIds) { ret.add(searchUserById(ldap, userId)); } return ret; } catch (Exception e) { LOG.debug(MessageFormat.format("Failed to retrieve users {0}", CollectionUtils.toString(userIds, ',')), e); return Collections.emptySet(); } finally { closeQuietly(ldap); } } public List<User> searchUserByName(String name) { LdapContext ldap = null; try { ldap = getLdapContext(); return searchUserByName(ldap, name); } catch (Exception e) { LOG.debug(MessageFormat.format("Failed to search user ''{0}''", name), e); return Collections.emptyList(); } finally { closeQuietly(ldap); } } private User searchUserById(LdapContext ldap, String userId) throws NamingException { SearchControls sc = getSearchControls(); NamingEnumeration<SearchResult> results = null; try { results = ldap.search(config.getBaseDN(), MessageFormat.format("(&(objectClass=user)(sAMAccountName={0}))", userId), sc); //$NON-NLS-1$ while (results != null && results.hasMore()) { SearchResult entry = results.next(); User user = processEntry(entry); if (user != null) { if (LOG.isDebugEnabled()) { LOG.debug(MessageFormat.format("Success reading from LDAP: {0}, {1} <{2}>", //$NON-NLS-1$ user.getUserId(), user.getDisplayName(), user.getEmail())); } return user; } } } finally { closeQuietly(results); } return new User(userId); } private List<User> searchUserByName(LdapContext ldap, String name) throws NamingException { List<User> ret = new ArrayList<User>(0); try { boolean somethingAdded = false; SearchControls sc = getSearchControls(); String[] parts = StringUtils.split(NormalizeUtil.normalize(name), " ,"); //$NON-NLS-1$ if (parts.length == 1) { somethingAdded = search(parts[0], ret, ldap, sc); } else if (parts.length > 1) { // givenname surname ('Michael Ochmann'), or surname givenname('Ochmann, Michael') NamingEnumeration<SearchResult> results = null; try { results = ldap.search(config.getBaseDN(), MessageFormat.format("(&(objectClass=user)(givenName={0}*)(sn={1}*))", //$NON-NLS-1$ parts[0], parts[1]), sc); somethingAdded |= addLDAPSearchResult(ret, results); } finally { closeQuietly(results); } try { results = ldap.search(config.getBaseDN(), MessageFormat.format("(&(objectClass=user)(sn={0}*)(givenName={1}*))", //$NON-NLS-1$ parts[0], parts[1]), sc); somethingAdded |= addLDAPSearchResult(ret, results); } finally { closeQuietly(results); } // givenname initial surname, e.g. 'Michael R. Ochmann' if (parts.length > 2) { try { results = ldap.search(config.getBaseDN(), MessageFormat.format("(&(objectClass=user)(givenName={0}*)(sn={1}*))", //$NON-NLS-1$ parts[0], parts[2]), sc); somethingAdded |= addLDAPSearchResult(ret, results); } finally { closeQuietly(results); } try { results = ldap.search(config.getBaseDN(), MessageFormat.format("(&(objectClass=user)(sn={0}*)(givenName={1}*))", //$NON-NLS-1$ parts[0], parts[2]), sc); somethingAdded |= addLDAPSearchResult(ret, results); } finally { closeQuietly(results); } } if (!somethingAdded) { // try to match each part individually for (int i = 0; i < parts.length; ++i) { somethingAdded = search(parts[i], ret, ldap, sc); } } } } catch (SizeLimitExceededException e) { // 1000 is good enough at the moment for this use case... LOG.warn(MessageFormat.format("LDAP query size limit exceeded while searching for ''{0}''", name), e); } return ret; } private boolean search(String s, List<User> ret, LdapContext ldap, SearchControls sc) throws NamingException { // try a match with surname* boolean somethingAdded = false; NamingEnumeration<SearchResult> results = null; try { results = ldap.search(config.getBaseDN(), MessageFormat.format("(&(objectClass=user)(|(sn={0}*)(givenName={1}*)))", s, s), sc); //$NON-NLS-1$ somethingAdded = addLDAPSearchResult(ret, results); } finally { closeQuietly(results); } if (!somethingAdded) { try { // try a match with the account name and mail address results = ldap.search(config.getBaseDN(), MessageFormat.format("(&(objectClass=user)(sAMAccountName={0}*))", s), sc); //$NON-NLS-1$ somethingAdded |= addLDAPSearchResult(ret, results); } finally { closeQuietly(results); } if (!somethingAdded) { try { // try to match surname~= or givenname~= results = ldap.search(config.getBaseDN(), MessageFormat.format("(&(objectClass=user)(|(sn~={0})(givenName~={1})))", s, s), sc); //$NON-NLS-1$ somethingAdded |= addLDAPSearchResult(ret, results); } finally { closeQuietly(results); } if (!somethingAdded) { try { results = ldap.search(config.getBaseDN(), MessageFormat.format("(&(objectClass=user)(mail={0}*))", s), sc); //$NON-NLS-1$ somethingAdded |= addLDAPSearchResult(ret, results); } finally { closeQuietly(results); } } } } return somethingAdded; } // Iterate over a batch of search results sent by the server private boolean addLDAPSearchResult(List<User> users, NamingEnumeration<SearchResult> results) throws NamingException { boolean somethingAdded = false; while (results != null && results.hasMore()) { // Display an entry SearchResult entry = results.next(); User user = processEntry(entry); if (user != null) { if (LOG.isDebugEnabled()) { LOG.debug(MessageFormat.format("Success reading from LDAP: {0}, {1} <{2}>", user.getUserId(), user.getDisplayName(), user.getEmail())); } users.add(user); somethingAdded = true; } } return somethingAdded; } private String getStringValue(Attributes attributes, LDAPAttributeNames attributeName) throws NamingException { String ret = null; Attribute attribute = attributes.get(attributeName.getLdapKey()); if (attribute != null) { for (int i = 0; i < attribute.size(); i++) { ret = (String) attribute.get(i); } } return ret; } private User processEntry(SearchResult entry) throws NamingException { User user = new User(); Attributes attrs = entry.getAttributes(); Attribute attrBits = attrs.get(LDAPAttributeNames.BITS.getLdapKey()); if (attrBits != null) { long lng = Long.parseLong(attrBits.get(0).toString()); long secondBit = lng & 2; // get bit 2 if (secondBit != 0) { // User not enabled return null; } } user.setUserId(StringUtils.lowerCase(getStringValue(attrs, LDAPAttributeNames.USERID))); user.setFirstname(getStringValue(attrs, LDAPAttributeNames.FIRSTNAME)); user.setLastname(getStringValue(attrs, LDAPAttributeNames.LASTNAME)); user.setEmail(getStringValue(attrs, LDAPAttributeNames.EMAIL)); user.setTelephone(getStringValue(attrs, LDAPAttributeNames.TELEPHONE)); user.setMobile(getStringValue(attrs, LDAPAttributeNames.MOBILE)); user.setRoom(getStringValue(attrs, LDAPAttributeNames.ROOM)); user.setLocation(getStringValue(attrs, LDAPAttributeNames.LOCATION)); user.setDepartment(getStringValue(attrs, LDAPAttributeNames.DEPARTMENT)); user.setCompany(getStringValue(attrs, LDAPAttributeNames.COMPANY)); user.setSip(getStringValue(attrs, LDAPAttributeNames.SIP)); return user; } @SuppressWarnings("nls") private SearchControls getSearchControls() { SearchControls sc = new SearchControls(); if ("base".equalsIgnoreCase(config.getSearchScope())) { sc.setSearchScope(SearchControls.OBJECT_SCOPE); } else if ("onelevel".equalsIgnoreCase(config.getSearchScope())) { sc.setSearchScope(SearchControls.ONELEVEL_SCOPE); } else if ("subtree".equalsIgnoreCase(config.getSearchScope())) { sc.setSearchScope(SearchControls.SUBTREE_SCOPE); } sc.setReturningAttributes(LDAPAttributeNames.getAll()); return sc; } }