/* * Copyright (C) 2003-2007 eXo Platform SAS. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see. */ package org.exoplatform.services.ldap.impl; import java.io.File; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.CommunicationException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.ServiceUnavailableException; 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.exoplatform.services.log.Log; import org.exoplatform.container.ExoContainer; import org.exoplatform.container.component.ComponentPlugin; import org.exoplatform.container.component.ComponentRequestLifecycle; import org.exoplatform.container.xml.InitParams; import org.exoplatform.services.ldap.CreateObjectCommand; import org.exoplatform.services.ldap.DeleteObjectCommand; import org.exoplatform.services.ldap.LDAPService; import org.exoplatform.services.log.ExoLogger; /** * Created by The eXo Platform SAS . Author : James Chamberlain * james@echamberlains.com Date: 11/2/2005 */ public class LDAPServiceImpl implements LDAPService, ComponentRequestLifecycle { private static final Log LOG = ExoLogger.getLogger(LDAPServiceImpl.class.getName()); private Map env = new HashMap(); private int serverType = DEFAULT_SERVER; /** * @param params See {@link InitParams} */ public LDAPServiceImpl(InitParams params) { LDAPConnectionConfig config = (LDAPConnectionConfig) params.getObjectParam("ldap.config") .getObject(); String url = config.getProviderURL(); serverType = toServerType(config.getServerName()); boolean ssl = url.toLowerCase().startsWith("ldaps"); if (serverType == ACTIVE_DIRECTORY_SERVER && ssl) { String keystore = System.getProperty("java.home"); keystore += File.separator + "lib" + File.separator + "security" + File.separator + "cacerts"; System.setProperty("javax.net.ssl.trustStore", keystore); } env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.SECURITY_AUTHENTICATION, config.getAuthenticationType()); env.put(Context.SECURITY_PRINCIPAL, config.getRootDN()); env.put(Context.SECURITY_CREDENTIALS, config.getPassword()); // TODO move it in configuration ? env.put("com.sun.jndi.ldap.connect.timeout", "60000"); env.put("com.sun.jndi.ldap.connect.pool", "true"); env.put("java.naming.ldap.version", config.getVerion()); env.put("java.naming.ldap.attributes.binary", "tokenGroups"); env.put(Context.REFERRAL, config.getReferralMode()); Pattern pattern = Pattern.compile("\\p{Space}*,\\p{Space}*", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(url); if (ssl) url = matcher.replaceAll("/ ldaps://"); else url = matcher.replaceAll("/ ldap://"); url += "/"; env.put(Context.PROVIDER_URL, url); if (serverType == ACTIVE_DIRECTORY_SERVER && ssl) env.put(Context.SECURITY_PROTOCOL, "ssl"); } /** * {@inheritDoc} */ public LdapContext getLdapContext() throws NamingException { // This method can be used for getting context from thread-local variables, // etc. instead create new instance of LdapContext. Currently just create // new one (use from pool if 'com.sun.jndi.ldap.connect.pool' is 'true'). // Override this method if need other behavior. return getLdapContext(true); } /** * {@inheritDoc} */ public LdapContext getLdapContext(boolean renew) throws NamingException { // Force create new context. return new InitialLdapContext(new Hashtable(env), null); } /** * {@inheritDoc} */ public void release(LdapContext ctx) { // Just close since we are not pooling anything by self. // Override this method if need other behavior. try { if (ctx != null) ctx.close(); } catch (NamingException e) { LOG.warn("Exception occur when try close LDAP context. ", e); } } /** * {@inheritDoc} */ public InitialContext getInitialContext() throws NamingException { Hashtable props = new Hashtable(env); props.put(Context.OBJECT_FACTORIES, "com.sun.jndi.ldap.obj.LdapGroupFactory"); props.put(Context.STATE_FACTORIES, "com.sun.jndi.ldap.obj.LdapGroupFactory"); return new InitialLdapContext(props, null); } /** * {@inheritDoc} */ public boolean authenticate(String userDN, String password) throws NamingException { Hashtable props = new Hashtable(env); props.put(Context.SECURITY_AUTHENTICATION, "simple"); props.put(Context.SECURITY_PRINCIPAL, userDN); props.put(Context.SECURITY_CREDENTIALS, password); props.put("com.sun.jndi.ldap.connect.pool", "false"); try { new InitialLdapContext(props, null); return true; } catch (NamingException e) { if (LOG.isDebugEnabled()) e.printStackTrace(); return false; } } /** * {@inheritDoc} */ public int getServerType() { return serverType; } /** * Delete objects from context. * * @param plugin see {@link DeleteObjectCommand} {@link ComponentPlugin} * @throws NamingException if {@link NamingException} occurs */ public void addDeleteObject(ComponentPlugin plugin) throws NamingException { if (false&&plugin instanceof DeleteObjectCommand) { DeleteObjectCommand command = (DeleteObjectCommand) plugin; List objectsToDelete = command.getObjectsToDelete(); if (objectsToDelete == null || objectsToDelete.size() == 0) return; LdapContext ctx = getLdapContext(); for (String name : objectsToDelete) { try { try { unbind(ctx, name); } catch (CommunicationException e1) { // create new LDAP context ctx = getLdapContext(true); // try repeat operation where communication error occurs unbind(ctx, name); } catch (ServiceUnavailableException e2) { // do the same as for CommunicationException ctx = getLdapContext(true); // unbind(ctx, name); } } catch (Exception e3) { // Catch all exceptions here. // Just inform about exception if it is not connection problem. LOG.error("Remove object (" + name + ") failed. ", e3); } } // close context release(ctx); } } private void unbind(LdapContext ctx, String name) throws NamingException { SearchControls constraints = new SearchControls(); constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE); NamingEnumeration results = ctx.search(name, "(objectclass=*)", constraints); while (results.hasMore()) { SearchResult sr = results.next(); unbind(ctx, sr.getNameInNamespace()); } // close search results enumeration results.close(); ctx.unbind(name); } /** * Create objects in context. * * @param plugin see {@link CreateObjectCommand} {@link ComponentPlugin} * @throws NamingException if {@link NamingException} occurs */ public void addCreateObject(ComponentPlugin plugin) throws NamingException { if (plugin instanceof CreateObjectCommand) { CreateObjectCommand command = (CreateObjectCommand) plugin; Map objectsToCreate = command.getObjectsToCreate(); if (objectsToCreate == null || objectsToCreate.size() == 0) return; LdapContext ctx = getLdapContext(); for (Map.Entry e : objectsToCreate.entrySet()) { String name = e.getKey(); Attributes attrs = e.getValue(); try { try { ctx.createSubcontext(name, attrs); } catch (CommunicationException e1) { // create new LDAP context ctx = getLdapContext(true); // try repeat operation where communication error occurs ctx.createSubcontext(name, attrs); } catch (ServiceUnavailableException e2) { // do the same as for CommunicationException ctx = getLdapContext(true); // ctx.createSubcontext(name, attrs); } } catch (Exception e3) { // Catch all exceptions here. // just inform about exception if it is not connection problem. LOG.error("Create object (" + name + ") failed. ", e3); } } release(ctx); } } /** * {@inheritDoc} * * @deprecated Will be removed */ public void startRequest(ExoContainer container) { } /** * {@inheritDoc} * * @deprecated Will be removed */ public void endRequest(ExoContainer container) { // LdapContext context = tlocal_.get(); // if (context != null) { // try { // context.close(); // tlocal_.set(null); // } catch (Exception ex) { // ex.printStackTrace(); // } // } } private int toServerType(String name) { name = name.trim(); if (name == null || name.length() < 1) return DEFAULT_SERVER; if (name.equalsIgnoreCase("ACTIVE.DIRECTORY")) return ACTIVE_DIRECTORY_SERVER; // if(name.equalsIgnoreCase("OPEN.LDAP"))return OPEN_LDAP_SERVER; // if(name.equalsIgnoreCase("NETSCAPE.DIRECTORY")) return NETSCAPE_SERVER; // if(name.equalsIgnoreCase("REDHAT.DIRECTORY")) return REDHAT_SERVER; return DEFAULT_SERVER; } }