001package org.apache.archiva.redback.common.ldap.connection;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.commons.lang3.StringUtils;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026import javax.naming.Context;
027import javax.naming.NamingException;
028import javax.naming.directory.DirContext;
029import javax.naming.ldap.LdapName;
030import javax.naming.ldap.Rdn;
031import java.io.IOException;
032import java.util.Collections;
033import java.util.Hashtable;
034import java.util.List;
035import java.util.Properties;
036import java.util.concurrent.atomic.AtomicBoolean;
037import javax.naming.spi.NamingManager;
038
039/**
040 * The configuration for a connection will not change.
041 *
042 * @author <a href="mailto:trygvis@inamo.no">trygvis</a>
043 * @since 2.1
044 */
045public class DefaultLdapConnection
046    implements LdapConnection
047{
048
049    private Logger log = LoggerFactory.getLogger( getClass() );
050
051    private LdapConnectionConfiguration config;
052
053    private DirContext context;
054
055    private List<Rdn> baseDnRdns;
056
057    private AtomicBoolean open = new AtomicBoolean( false );
058
059    public DefaultLdapConnection( LdapConnectionConfiguration config, Rdn subRdn )
060        throws LdapException
061    {
062        this.config = config;
063
064        if( config.getBaseDn() == null ) {
065            throw new LdapException( "Invalid BaseDn in the configuration." );
066        }
067
068        LdapName baseDn = new LdapName( config.getBaseDn().getRdns() );
069
070        if ( subRdn != null )
071        {
072            baseDn.add( subRdn );
073        }
074
075        log.debug( "baseDn: {}", baseDn );
076
077        baseDnRdns = Collections.unmodifiableList( baseDn.getRdns() );
078
079        if ( context != null )
080        {
081            throw new LdapException( "Already connected." );
082        }
083
084        log.debug( "baseDnRdns: {}", baseDnRdns );
085
086        Hashtable<Object, Object> e = getEnvironment();
087
088        try
089        {
090            context = (DirContext) NamingManager.getInitialContext( e );
091            this.open.set( true );
092        }
093        catch ( NamingException ex )
094        {
095            throw new LdapException( "Could not connect to the server.", ex );
096        }
097    }
098
099    /**
100     * This ldap connection will attempt to establish a connection using the configuration,
101     * replacing the principal and the password
102     *
103     * @param config
104     * @param bindDn
105     * @param password
106     * @throws LdapException
107     */
108    public DefaultLdapConnection( LdapConnectionConfiguration config, String bindDn, String password )
109        throws LdapException
110    {
111        this.config = config;
112
113        Hashtable<Object, Object> e = getEnvironment();
114
115        e.put( Context.SECURITY_PRINCIPAL, bindDn );
116        e.put( Context.SECURITY_CREDENTIALS, password );
117
118        try
119        {
120            context = (DirContext) NamingManager.getInitialContext( e );
121            this.open.set( true );
122        }
123        catch ( NamingException ex )
124        {
125            throw new LdapException( "Could not connect to the server.", ex );
126        }
127    }
128
129    // ----------------------------------------------------------------------
130    // Connection Managment
131    // ----------------------------------------------------------------------
132
133    @Override
134    public Hashtable<Object, Object> getEnvironment()
135        throws LdapException
136    {
137        Properties env = new Properties();
138
139        env.putAll( config.getExtraProperties() );
140
141        config.check();
142
143        env.put( Context.INITIAL_CONTEXT_FACTORY, config.getContextFactory() );
144
145        // REDBACK-289/MRM-1488
146        // enable connection pooling when using Sun's LDAP context factory
147        if ( config.getContextFactory().equals( "com.sun.jndi.ldap.LdapCtxFactory" ) )
148        {
149            env.put( "com.sun.jndi.ldap.connect.pool", "true" );
150
151            env.put( "com.sun.jndi.ldap.connect.pool.timeout", "3600" );
152        }
153
154        if ( config.getHostname() != null )
155        {
156            String protocol = "ldap";// config.isSsl() ? "ldaps" : "ldap";
157            if ( config.getPort() != 0 )
158            {
159                env.put( Context.PROVIDER_URL, protocol + "://" + config.getHostname() + ":" + config.getPort() + "/" );
160            }
161            else
162            {
163                env.put( Context.PROVIDER_URL, protocol + "://" + config.getHostname() + "/" );
164            }
165        }
166
167        if ( config.isSsl() )
168        {
169            env.put( Context.SECURITY_PROTOCOL, "ssl" );
170        }
171
172        if ( config.getAuthenticationMethod() != null )
173        {
174            env.put( Context.SECURITY_AUTHENTICATION, config.getAuthenticationMethod() );
175        }
176
177        if ( config.getBindDn() != null )
178        {
179            env.put( Context.SECURITY_PRINCIPAL, config.getBindDn().toString() );
180        }
181
182        if ( config.getPassword() != null )
183        {
184            env.put( Context.SECURITY_CREDENTIALS, config.getPassword() );
185        }
186
187        // ----------------------------------------------------------------------
188        // Object Factories
189        // ----------------------------------------------------------------------
190
191        String objectFactories = null;
192
193        for ( Class<?> objectFactoryClass : config.getObjectFactories() )
194        {
195            if ( objectFactories == null )
196            {
197                objectFactories = objectFactoryClass.getName();
198            }
199            else
200            {
201                objectFactories += ":" + objectFactoryClass.getName();
202            }
203        }
204
205        if ( objectFactories != null )
206        {
207            env.setProperty( Context.OBJECT_FACTORIES, objectFactories );
208        }
209
210        // ----------------------------------------------------------------------
211        // State Factories
212        // ----------------------------------------------------------------------
213
214        String stateFactories = null;
215
216        for ( Class<?> stateFactoryClass : config.getStateFactories() )
217        {
218            if ( stateFactories == null )
219            {
220                stateFactories = stateFactoryClass.getName();
221            }
222            else
223            {
224                stateFactories += ":" + stateFactoryClass.getName();
225            }
226        }
227
228        if ( stateFactories != null )
229        {
230            env.setProperty( Context.STATE_FACTORIES, stateFactories );
231        }
232
233        log.debug( "env properties: {}", env );
234
235        return env;
236    }
237
238    @Override
239    public void close() throws NamingException
240    {
241        if (this.open.compareAndSet( true, false ))
242        {
243            try
244            {
245                if ( context != null )
246                {
247                    context.close( );
248                }
249            }
250            catch ( NamingException ex )
251            {
252                log.info( "skip error closing ldap connection {}", ex.getMessage( ) );
253                throw ex;
254            }
255            finally
256            {
257                this.context = null;
258            }
259        } else {
260            log.warn( "Connection already closed "+this.baseDnRdns );
261        }
262    }
263
264    // ----------------------------------------------------------------------
265    // Utils
266    // ----------------------------------------------------------------------
267
268    @Override
269    public LdapConnectionConfiguration getConfiguration()
270    {
271        return config;
272    }
273
274    @Override
275    public List<Rdn> getBaseDnRdns()
276    {
277        return baseDnRdns;
278    }
279
280    @Override
281    public DirContext getDirContext()
282    {
283        if (this.open.get())
284        {
285            return context;
286        } else {
287            throw new RuntimeException( "Connection closed " + this.getBaseDnRdns( ) );
288        }
289    }
290}