001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.server.unit;
021    
022    
023    import java.io.File;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.HashMap;
029    import java.util.Hashtable;
030    import java.util.List;
031    import java.util.Map;
032    
033    import javax.naming.Context;
034    import javax.naming.NamingException;
035    import javax.naming.ldap.InitialLdapContext;
036    import javax.naming.ldap.LdapContext;
037    
038    import junit.framework.AssertionFailedError;
039    import junit.framework.TestCase;
040    
041    import org.apache.commons.io.FileUtils;
042    import org.apache.directory.server.constants.ServerDNConstants;
043    import org.apache.directory.server.core.CoreSession;
044    import org.apache.directory.server.core.DefaultDirectoryService;
045    import org.apache.directory.server.core.DirectoryService;
046    import org.apache.directory.server.core.entry.DefaultServerEntry;
047    import org.apache.directory.server.core.jndi.CoreContextFactory;
048    import org.apache.directory.server.ldap.LdapServer;
049    import org.apache.directory.server.ldap.handlers.bind.MechanismHandler;
050    import org.apache.directory.server.ldap.handlers.bind.cramMD5.CramMd5MechanismHandler;
051    import org.apache.directory.server.ldap.handlers.bind.digestMD5.DigestMd5MechanismHandler;
052    import org.apache.directory.server.ldap.handlers.bind.gssapi.GssapiMechanismHandler;
053    import org.apache.directory.server.ldap.handlers.bind.ntlm.NtlmMechanismHandler;
054    import org.apache.directory.server.ldap.handlers.bind.plain.PlainMechanismHandler;
055    import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler;
056    import org.apache.directory.server.ldap.handlers.extended.StoredProcedureExtendedOperationHandler;
057    import org.apache.directory.server.protocol.shared.transport.TcpTransport;
058    import org.apache.directory.shared.ldap.constants.SupportedSaslMechanisms;
059    import org.apache.directory.shared.ldap.entry.Entry;
060    import org.apache.directory.shared.ldap.entry.EntryAttribute;
061    import org.apache.directory.shared.ldap.entry.Value;
062    import org.apache.directory.shared.ldap.exception.LdapConfigurationException;
063    import org.apache.directory.shared.ldap.ldif.LdifEntry;
064    import org.apache.directory.shared.ldap.ldif.LdifReader;
065    import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
066    import org.apache.mina.util.AvailablePortFinder;
067    
068    import org.slf4j.Logger;
069    import org.slf4j.LoggerFactory;
070    
071    
072    /**
073     * A simple testcase for testing JNDI provider functionality.
074     *
075     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
076     * @version $Rev: 784530 $
077     */
078    public abstract class AbstractServerTest extends TestCase
079    {
080        private static final Logger LOG = LoggerFactory.getLogger( AbstractServerTest.class );
081        private static final List<LdifEntry> EMPTY_LIST = Collections.unmodifiableList( new ArrayList<LdifEntry>( 0 ) );
082        private static final String CTX_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
083    
084        /** the context root for the system partition */
085        protected LdapContext sysRoot;
086    
087        /** the context root for the rootDSE */
088        protected CoreSession rootDSE;
089    
090        /** the context root for the schema */
091        protected LdapContext schemaRoot;
092    
093        /** flag whether to delete database files for each test or not */
094        protected boolean doDelete = true;
095    
096        protected int port = -1;
097    
098        private static int start;
099        private static long t0;
100        protected static int nbTests = 10000;
101        protected DirectoryService directoryService;
102        protected NioSocketAcceptor socketAcceptor;
103        protected LdapServer ldapServer;
104    
105    
106        /**
107         * If there is an LDIF file with the same name as the test class 
108         * but with the .ldif extension then it is read and the entries 
109         * it contains are added to the server.  It appears as though the
110         * administor adds these entries to the server.
111         *
112         * @param verifyEntries whether or not all entry additions are checked
113         * to see if they were in fact correctly added to the server
114         * @return a list of entries added to the server in the order they were added
115         * @throws NamingException of the load fails
116         */
117        protected List<LdifEntry> loadTestLdif( boolean verifyEntries ) throws Exception
118        {
119            return loadLdif( getClass().getResourceAsStream( getClass().getSimpleName() + ".ldif" ), verifyEntries );
120        }
121    
122    
123        /**
124         * Loads an LDIF from an input stream and adds the entries it contains to 
125         * the server.  It appears as though the administrator added these entries
126         * to the server.
127         *
128         * @param in the input stream containing the LDIF entries to load
129         * @param verifyEntries whether or not all entry additions are checked
130         * to see if they were in fact correctly added to the server
131         * @return a list of entries added to the server in the order they were added
132         * @throws NamingException of the load fails
133         */
134        protected List<LdifEntry> loadLdif( InputStream in, boolean verifyEntries ) throws Exception
135        {
136            if ( in == null )
137            {
138                return EMPTY_LIST;
139            }
140            
141            LdifReader ldifReader = new LdifReader( in );
142            List<LdifEntry> entries = new ArrayList<LdifEntry>();
143    
144            for ( LdifEntry entry:ldifReader )
145            {
146                rootDSE.add( 
147                    new DefaultServerEntry( directoryService.getRegistries(), entry.getEntry() ) ); 
148                
149                if ( verifyEntries )
150                {
151                    verify( entry );
152                    LOG.info( "Successfully verified addition of entry {}", entry.getDn() );
153                }
154                else
155                {
156                    LOG.info( "Added entry {} without verification", entry.getDn() );
157                }
158                
159                entries.add( entry );
160            }
161            
162            return entries;
163        }
164        
165    
166        /**
167         * Verifies that an entry exists in the directory with the 
168         * specified attributes.
169         *
170         * @param entry the entry to verify
171         * @throws NamingException if there are problems accessing the entry
172         */
173        protected void verify( LdifEntry entry ) throws Exception
174        {
175            Entry readEntry = rootDSE.lookup( entry.getDn() );
176            
177            for ( EntryAttribute readAttribute:readEntry )
178            {
179                String id = readAttribute.getId();
180                EntryAttribute origAttribute = entry.getEntry().get( id );
181                
182                for ( Value<?> value:origAttribute )
183                {
184                    if ( ! readAttribute.contains( value ) )
185                    {
186                        LOG.error( "Failed to verify entry addition of {}. {} attribute in original " +
187                                "entry missing from read entry.", entry.getDn(), id );
188                        throw new AssertionFailedError( "Failed to verify entry addition of " + entry.getDn()  );
189                    }
190                }
191            }
192        }
193        
194    
195        /**
196         * Common code to get an initial context via a simple bind to the 
197         * server over the wire using the SUN JNDI LDAP provider. Do not use 
198         * this method until after the setUp() method is called to start the
199         * server otherwise it will fail. 
200         *
201         * @return an LDAP context as the the administrator to the rootDSE
202         * @throws NamingException if the server cannot be contacted
203         */
204        protected LdapContext getWiredContext() throws Exception
205        {
206            return getWiredContext( ServerDNConstants.ADMIN_SYSTEM_DN, "secret" );
207        }
208        
209        
210        /**
211         * Common code to get an initial context via a simple bind to the 
212         * server over the wire using the SUN JNDI LDAP provider. Do not use 
213         * this method until after the setUp() method is called to start the
214         * server otherwise it will fail.
215         *
216         * @param bindPrincipalDn the DN of the principal to bind as
217         * @param password the password of the bind principal
218         * @return an LDAP context as the the administrator to the rootDSE
219         * @throws NamingException if the server cannot be contacted
220         */
221        protected LdapContext getWiredContext( String bindPrincipalDn, String password ) throws Exception
222        {
223    //        if ( ! apacheDS.isStarted() )
224    //        {
225    //            throw new ConfigurationException( "The server is not online! Cannot connect to it." );
226    //        }
227            
228            Hashtable<String, String> env = new Hashtable<String, String>();
229            env.put( Context.INITIAL_CONTEXT_FACTORY, CTX_FACTORY );
230            env.put( Context.PROVIDER_URL, "ldap://localhost:" + port );
231            env.put( Context.SECURITY_PRINCIPAL, bindPrincipalDn );
232            env.put( Context.SECURITY_CREDENTIALS, password );
233            env.put( Context.SECURITY_AUTHENTICATION, "simple" );
234            return new InitialLdapContext( env, null );
235        }
236        
237        
238        /**
239         * Get's the initial context factory for the provider's ou=system context
240         * root.
241         *
242         * @see junit.framework.TestCase#setUp()
243         */
244        protected void setUp() throws Exception
245        {
246            super.setUp();
247            
248            if ( start == 0 )
249            {
250                t0 = System.currentTimeMillis();
251            }
252    
253            start++;
254            directoryService = new DefaultDirectoryService();
255            directoryService.setShutdownHookEnabled( false );
256            port = AvailablePortFinder.getNextAvailable( 1024 );
257            ldapServer = new LdapServer();
258            ldapServer.setTransports( new TcpTransport( port ) );
259            ldapServer.setDirectoryService( directoryService );
260    
261            setupSaslMechanisms( ldapServer );
262    
263            doDelete( directoryService.getWorkingDirectory() );
264            configureDirectoryService();
265            directoryService.startup();
266    
267            configureLdapServer();
268    
269            // TODO shouldn't this be before calling configureLdapServer() ???
270            ldapServer.addExtendedOperationHandler( new StartTlsHandler() );
271            ldapServer.addExtendedOperationHandler( new StoredProcedureExtendedOperationHandler() );
272    
273            ldapServer.start();
274            setContexts( ServerDNConstants.ADMIN_SYSTEM_DN, "secret" );
275        }
276    
277    
278        private void setupSaslMechanisms( LdapServer server )
279        {
280            Map<String, MechanismHandler> mechanismHandlerMap = new HashMap<String,MechanismHandler>();
281    
282            mechanismHandlerMap.put( SupportedSaslMechanisms.PLAIN, new PlainMechanismHandler() );
283    
284            CramMd5MechanismHandler cramMd5MechanismHandler = new CramMd5MechanismHandler();
285            mechanismHandlerMap.put( SupportedSaslMechanisms.CRAM_MD5, cramMd5MechanismHandler );
286    
287            DigestMd5MechanismHandler digestMd5MechanismHandler = new DigestMd5MechanismHandler();
288            mechanismHandlerMap.put( SupportedSaslMechanisms.DIGEST_MD5, digestMd5MechanismHandler );
289    
290            GssapiMechanismHandler gssapiMechanismHandler = new GssapiMechanismHandler();
291            mechanismHandlerMap.put( SupportedSaslMechanisms.GSSAPI, gssapiMechanismHandler );
292    
293            NtlmMechanismHandler ntlmMechanismHandler = new NtlmMechanismHandler();
294            // TODO - set some sort of default NtlmProvider implementation here
295            // ntlmMechanismHandler.setNtlmProvider( provider );
296            // TODO - or set FQCN of some sort of default NtlmProvider implementation here
297            // ntlmMechanismHandler.setNtlmProviderFqcn( "com.foo.BarNtlmProvider" );
298            mechanismHandlerMap.put( SupportedSaslMechanisms.NTLM, ntlmMechanismHandler );
299            mechanismHandlerMap.put( SupportedSaslMechanisms.GSS_SPNEGO, ntlmMechanismHandler );
300    
301            ldapServer.setSaslMechanismHandlers( mechanismHandlerMap );
302        }
303    
304    
305        protected void configureDirectoryService() throws Exception
306        {
307        }
308    
309    
310        protected void configureLdapServer()
311        {
312        }
313    
314    
315        /**
316         * Deletes the Eve working directory.
317         * @param wkdir the directory to delete
318         * @throws IOException if the directory cannot be deleted
319         */
320        protected void doDelete( File wkdir ) throws IOException
321        {
322            if ( doDelete )
323            {
324                if ( wkdir.exists() )
325                {
326                    FileUtils.deleteDirectory( wkdir );
327                }
328    
329                if ( wkdir.exists() )
330                {
331                    throw new IOException( "Failed to delete: " + wkdir );
332                }
333            }
334        }
335    
336    
337        /**
338         * Sets the contexts for this base class.  Values of user and password used to
339         * set the respective JNDI properties.  These values can be overriden by the
340         * overrides properties.
341         *
342         * @param user the username for authenticating as this user
343         * @param passwd the password of the user
344         * @throws NamingException if there is a failure of any kind
345         */
346        protected void setContexts( String user, String passwd ) throws Exception
347        {
348            Hashtable<String, Object> env = new Hashtable<String, Object>();
349            env.put( DirectoryService.JNDI_KEY, directoryService );
350            env.put( Context.SECURITY_PRINCIPAL, user );
351            env.put( Context.SECURITY_CREDENTIALS, passwd );
352            env.put( Context.SECURITY_AUTHENTICATION, "simple" );
353            env.put( Context.INITIAL_CONTEXT_FACTORY, CoreContextFactory.class.getName() );
354            setContexts( env );
355        }
356    
357    
358        /**
359         * Sets the contexts of this class taking into account the extras and overrides
360         * properties.  
361         *
362         * @param env an environment to use while setting up the system root.
363         * @throws NamingException if there is a failure of any kind
364         */
365        protected void setContexts( Hashtable<String, Object> env ) throws Exception
366        {
367            Hashtable<String, Object> envFinal = new Hashtable<String, Object>( env );
368            envFinal.put( Context.PROVIDER_URL, ServerDNConstants.SYSTEM_DN );
369            sysRoot = new InitialLdapContext( envFinal, null );
370    
371            envFinal.put( Context.PROVIDER_URL, "" );
372            rootDSE = directoryService.getAdminSession();
373    
374            envFinal.put( Context.PROVIDER_URL, ServerDNConstants.OU_SCHEMA_DN );
375            schemaRoot = new InitialLdapContext( envFinal, null );
376        }
377    
378    
379        /**
380         * Sets the system context root to null.
381         *
382         * @see junit.framework.TestCase#tearDown()
383         */
384        protected void tearDown() throws Exception
385        {
386            super.tearDown();
387            ldapServer.stop();
388            try
389            {
390                directoryService.shutdown();
391            }
392            catch ( Exception e )
393            {
394            }
395    
396            sysRoot = null;
397        }
398    
399        
400        /**
401         * Imports the LDIF entries packaged with the Eve JNDI provider jar into
402         * the newly created system partition to prime it up for operation.  Note
403         * that only ou=system entries will be added - entries for other partitions
404         * cannot be imported and will blow chunks.
405         *
406         * @throws NamingException if there are problems reading the ldif file and
407         * adding those entries to the system partition
408         * @param in the input stream with the ldif
409         */
410        protected void importLdif( InputStream in ) throws NamingException
411        {
412            try
413            {
414                for ( LdifEntry ldifEntry:new LdifReader( in ) )
415                {
416                    rootDSE.add( 
417                        new DefaultServerEntry( 
418                            rootDSE.getDirectoryService().getRegistries(), ldifEntry.getEntry() ) ); 
419                }
420            }
421            catch ( Exception e )
422            {
423                String msg = "failed while trying to parse system ldif file";
424                NamingException ne = new LdapConfigurationException( msg );
425                ne.setRootCause( e );
426                throw ne;
427            }
428        }
429    
430        
431        /**
432         * Inject an ldif String into the server. DN must be relative to the
433         * root.
434         * @param ldif the entries to inject
435         * @throws NamingException if the entries cannot be added
436         */
437        protected void injectEntries( String ldif ) throws Exception
438        {
439            LdifReader reader = new LdifReader();
440            List<LdifEntry> entries = reader.parseLdif( ldif );
441    
442            for ( LdifEntry entry : entries )
443            {
444                rootDSE.add( 
445                    new DefaultServerEntry( 
446                        rootDSE.getDirectoryService().getRegistries(), entry.getEntry() ) ); 
447            }
448        }
449    }