View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.unit;
21  
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.Hashtable;
30  import java.util.List;
31  import java.util.Map;
32  
33  import javax.naming.Context;
34  import javax.naming.NamingException;
35  import javax.naming.ldap.InitialLdapContext;
36  import javax.naming.ldap.LdapContext;
37  
38  import junit.framework.AssertionFailedError;
39  import junit.framework.TestCase;
40  
41  import org.apache.commons.io.FileUtils;
42  import org.apache.directory.server.constants.ServerDNConstants;
43  import org.apache.directory.server.core.CoreSession;
44  import org.apache.directory.server.core.DefaultDirectoryService;
45  import org.apache.directory.server.core.DirectoryService;
46  import org.apache.directory.server.core.entry.DefaultServerEntry;
47  import org.apache.directory.server.core.jndi.CoreContextFactory;
48  import org.apache.directory.server.ldap.LdapService;
49  import org.apache.directory.server.ldap.handlers.bind.MechanismHandler;
50  import org.apache.directory.server.ldap.handlers.bind.cramMD5.CramMd5MechanismHandler;
51  import org.apache.directory.server.ldap.handlers.bind.digestMD5.DigestMd5MechanismHandler;
52  import org.apache.directory.server.ldap.handlers.bind.gssapi.GssapiMechanismHandler;
53  import org.apache.directory.server.ldap.handlers.bind.ntlm.NtlmMechanismHandler;
54  import org.apache.directory.server.ldap.handlers.bind.plain.PlainMechanismHandler;
55  import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler;
56  import org.apache.directory.server.ldap.handlers.extended.StoredProcedureExtendedOperationHandler;
57  import org.apache.directory.server.protocol.shared.SocketAcceptor;
58  import org.apache.directory.shared.ldap.constants.SupportedSaslMechanisms;
59  import org.apache.directory.shared.ldap.entry.Entry;
60  import org.apache.directory.shared.ldap.entry.EntryAttribute;
61  import org.apache.directory.shared.ldap.entry.Value;
62  import org.apache.directory.shared.ldap.exception.LdapConfigurationException;
63  import org.apache.directory.shared.ldap.ldif.LdifEntry;
64  import org.apache.directory.shared.ldap.ldif.LdifReader;
65  import org.apache.mina.util.AvailablePortFinder;
66  
67  import org.slf4j.Logger;
68  import org.slf4j.LoggerFactory;
69  
70  
71  /**
72   * A simple testcase for testing JNDI provider functionality.
73   *
74   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
75   * @version $Rev: 692919 $
76   */
77  public abstract class AbstractServerTest extends TestCase
78  {
79      private static final Logger LOG = LoggerFactory.getLogger( AbstractServerTest.class );
80      private static final List<LdifEntry> EMPTY_LIST = Collections.unmodifiableList( new ArrayList<LdifEntry>( 0 ) );
81      private static final String CTX_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
82  
83      /** the context root for the system partition */
84      protected LdapContext sysRoot;
85  
86      /** the context root for the rootDSE */
87      protected CoreSession rootDSE;
88  
89      /** the context root for the schema */
90      protected LdapContext schemaRoot;
91  
92      /** flag whether to delete database files for each test or not */
93      protected boolean doDelete = true;
94  
95  //    protected ApacheDS apacheDS = new ApacheDS();
96  
97      protected int port = -1;
98  
99      private static int start;
100     private static long t0;
101     protected static int nbTests = 10000;
102     protected DirectoryService directoryService;
103     protected SocketAcceptor socketAcceptor;
104     protected LdapService ldapService;
105 
106 
107     /**
108      * If there is an LDIF file with the same name as the test class 
109      * but with the .ldif extension then it is read and the entries 
110      * it contains are added to the server.  It appears as though the
111      * administor adds these entries to the server.
112      *
113      * @param verifyEntries whether or not all entry additions are checked
114      * to see if they were in fact correctly added to the server
115      * @return a list of entries added to the server in the order they were added
116      * @throws NamingException of the load fails
117      */
118     protected List<LdifEntry> loadTestLdif( boolean verifyEntries ) throws Exception
119     {
120         return loadLdif( getClass().getResourceAsStream( getClass().getSimpleName() + ".ldif" ), verifyEntries );
121     }
122 
123 
124     /**
125      * Loads an LDIF from an input stream and adds the entries it contains to 
126      * the server.  It appears as though the administrator added these entries
127      * to the server.
128      *
129      * @param in the input stream containing the LDIF entries to load
130      * @param verifyEntries whether or not all entry additions are checked
131      * to see if they were in fact correctly added to the server
132      * @return a list of entries added to the server in the order they were added
133      * @throws NamingException of the load fails
134      */
135     protected List<LdifEntry> loadLdif( InputStream in, boolean verifyEntries ) throws Exception
136     {
137         if ( in == null )
138         {
139             return EMPTY_LIST;
140         }
141         
142         LdifReader ldifReader = new LdifReader( in );
143         List<LdifEntry> entries = new ArrayList<LdifEntry>();
144 
145         for ( LdifEntry entry:ldifReader )
146         {
147             rootDSE.add( 
148                 new DefaultServerEntry( directoryService.getRegistries(), entry.getEntry() ) ); 
149             
150             if ( verifyEntries )
151             {
152                 verify( entry );
153                 LOG.info( "Successfully verified addition of entry {}", entry.getDn() );
154             }
155             else
156             {
157                 LOG.info( "Added entry {} without verification", entry.getDn() );
158             }
159             
160             entries.add( entry );
161         }
162         
163         return entries;
164     }
165     
166 
167     /**
168      * Verifies that an entry exists in the directory with the 
169      * specified attributes.
170      *
171      * @param entry the entry to verify
172      * @throws NamingException if there are problems accessing the entry
173      */
174     protected void verify( LdifEntry entry ) throws Exception
175     {
176         Entry readEntry = rootDSE.lookup( entry.getDn() );
177         
178         for ( EntryAttribute readAttribute:readEntry )
179         {
180             String id = readAttribute.getId();
181             EntryAttribute origAttribute = entry.getEntry().get( id );
182             
183             for ( Value<?> value:origAttribute )
184             {
185                 if ( ! readAttribute.contains( value ) )
186                 {
187                     LOG.error( "Failed to verify entry addition of {}. {} attribute in original " +
188                             "entry missing from read entry.", entry.getDn(), id );
189                     throw new AssertionFailedError( "Failed to verify entry addition of " + entry.getDn()  );
190                 }
191             }
192         }
193     }
194     
195 
196     /**
197      * Common code to get an initial context via a simple bind to the 
198      * server over the wire using the SUN JNDI LDAP provider. Do not use 
199      * this method until after the setUp() method is called to start the
200      * server otherwise it will fail. 
201      *
202      * @return an LDAP context as the the administrator to the rootDSE
203      * @throws NamingException if the server cannot be contacted
204      */
205     protected LdapContext getWiredContext() throws Exception
206     {
207         return getWiredContext( ServerDNConstants.ADMIN_SYSTEM_DN, "secret" );
208     }
209     
210     
211     /**
212      * Common code to get an initial context via a simple bind to the 
213      * server over the wire using the SUN JNDI LDAP provider. Do not use 
214      * this method until after the setUp() method is called to start the
215      * server otherwise it will fail.
216      *
217      * @param bindPrincipalDn the DN of the principal to bind as
218      * @param password the password of the bind principal
219      * @return an LDAP context as the the administrator to the rootDSE
220      * @throws NamingException if the server cannot be contacted
221      */
222     protected LdapContext getWiredContext( String bindPrincipalDn, String password ) throws Exception
223     {
224 //        if ( ! apacheDS.isStarted() )
225 //        {
226 //            throw new ConfigurationException( "The server is not online! Cannot connect to it." );
227 //        }
228         
229         Hashtable<String, String> env = new Hashtable<String, String>();
230         env.put( Context.INITIAL_CONTEXT_FACTORY, CTX_FACTORY );
231         env.put( Context.PROVIDER_URL, "ldap://localhost:" + port );
232         env.put( Context.SECURITY_PRINCIPAL, bindPrincipalDn );
233         env.put( Context.SECURITY_CREDENTIALS, password );
234         env.put( Context.SECURITY_AUTHENTICATION, "simple" );
235         return new InitialLdapContext( env, null );
236     }
237     
238     
239     /**
240      * Get's the initial context factory for the provider's ou=system context
241      * root.
242      *
243      * @see junit.framework.TestCase#setUp()
244      */
245     protected void setUp() throws Exception
246     {
247         super.setUp();
248         
249         if ( start == 0 )
250         {
251             t0 = System.currentTimeMillis();
252         }
253 
254         start++;
255         directoryService = new DefaultDirectoryService();
256         directoryService.setShutdownHookEnabled( false );
257         socketAcceptor = new SocketAcceptor( null );
258         ldapService = new LdapService();
259         ldapService.setSocketAcceptor( socketAcceptor );
260         ldapService.setDirectoryService( directoryService );
261         ldapService.setIpPort( port = AvailablePortFinder.getNextAvailable( 1024 ) );
262 
263         setupSaslMechanisms( ldapService );
264 
265         doDelete( directoryService.getWorkingDirectory() );
266         configureDirectoryService();
267         directoryService.startup();
268 
269         configureLdapServer();
270 
271         // TODO shouldn't this be before calling configureLdapServer() ???
272         ldapService.addExtendedOperationHandler( new StartTlsHandler() );
273         ldapService.addExtendedOperationHandler( new StoredProcedureExtendedOperationHandler() );
274 
275         ldapService.start();
276         setContexts( ServerDNConstants.ADMIN_SYSTEM_DN, "secret" );
277     }
278 
279 
280     private void setupSaslMechanisms( LdapService server )
281     {
282         Map<String, MechanismHandler> mechanismHandlerMap = new HashMap<String,MechanismHandler>();
283 
284         mechanismHandlerMap.put( SupportedSaslMechanisms.PLAIN, new PlainMechanismHandler() );
285 
286         CramMd5MechanismHandler cramMd5MechanismHandler = new CramMd5MechanismHandler();
287         mechanismHandlerMap.put( SupportedSaslMechanisms.CRAM_MD5, cramMd5MechanismHandler );
288 
289         DigestMd5MechanismHandler digestMd5MechanismHandler = new DigestMd5MechanismHandler();
290         mechanismHandlerMap.put( SupportedSaslMechanisms.DIGEST_MD5, digestMd5MechanismHandler );
291 
292         GssapiMechanismHandler gssapiMechanismHandler = new GssapiMechanismHandler();
293         mechanismHandlerMap.put( SupportedSaslMechanisms.GSSAPI, gssapiMechanismHandler );
294 
295         NtlmMechanismHandler ntlmMechanismHandler = new NtlmMechanismHandler();
296         // TODO - set some sort of default NtlmProvider implementation here
297         // ntlmMechanismHandler.setNtlmProvider( provider );
298         // TODO - or set FQCN of some sort of default NtlmProvider implementation here
299         // ntlmMechanismHandler.setNtlmProviderFqcn( "com.foo.BarNtlmProvider" );
300         mechanismHandlerMap.put( SupportedSaslMechanisms.NTLM, ntlmMechanismHandler );
301         mechanismHandlerMap.put( SupportedSaslMechanisms.GSS_SPNEGO, ntlmMechanismHandler );
302 
303         ldapService.setSaslMechanismHandlers( mechanismHandlerMap );
304     }
305 
306 
307     protected void configureDirectoryService() throws Exception
308     {
309     }
310 
311 
312     protected void configureLdapServer()
313     {
314     }
315 
316 
317     /**
318      * Deletes the Eve working directory.
319      * @param wkdir the directory to delete
320      * @throws IOException if the directory cannot be deleted
321      */
322     protected void doDelete( File wkdir ) throws IOException
323     {
324         if ( doDelete )
325         {
326             if ( wkdir.exists() )
327             {
328                 FileUtils.deleteDirectory( wkdir );
329             }
330 
331             if ( wkdir.exists() )
332             {
333                 throw new IOException( "Failed to delete: " + wkdir );
334             }
335         }
336     }
337 
338 
339     /**
340      * Sets the contexts for this base class.  Values of user and password used to
341      * set the respective JNDI properties.  These values can be overriden by the
342      * overrides properties.
343      *
344      * @param user the username for authenticating as this user
345      * @param passwd the password of the user
346      * @throws NamingException if there is a failure of any kind
347      */
348     protected void setContexts( String user, String passwd ) throws Exception
349     {
350         Hashtable<String, Object> env = new Hashtable<String, Object>();
351         env.put( DirectoryService.JNDI_KEY, directoryService );
352         env.put( Context.SECURITY_PRINCIPAL, user );
353         env.put( Context.SECURITY_CREDENTIALS, passwd );
354         env.put( Context.SECURITY_AUTHENTICATION, "simple" );
355         env.put( Context.INITIAL_CONTEXT_FACTORY, CoreContextFactory.class.getName() );
356         setContexts( env );
357     }
358 
359 
360     /**
361      * Sets the contexts of this class taking into account the extras and overrides
362      * properties.  
363      *
364      * @param env an environment to use while setting up the system root.
365      * @throws NamingException if there is a failure of any kind
366      */
367     protected void setContexts( Hashtable<String, Object> env ) throws Exception
368     {
369         Hashtable<String, Object> envFinal = new Hashtable<String, Object>( env );
370         envFinal.put( Context.PROVIDER_URL, ServerDNConstants.SYSTEM_DN );
371         sysRoot = new InitialLdapContext( envFinal, null );
372 
373         envFinal.put( Context.PROVIDER_URL, "" );
374         rootDSE = directoryService.getAdminSession();
375 
376         envFinal.put( Context.PROVIDER_URL, ServerDNConstants.OU_SCHEMA_DN );
377         schemaRoot = new InitialLdapContext( envFinal, null );
378     }
379 
380 
381     /**
382      * Sets the system context root to null.
383      *
384      * @see junit.framework.TestCase#tearDown()
385      */
386     protected void tearDown() throws Exception
387     {
388         super.tearDown();
389         ldapService.stop();
390         try
391         {
392             directoryService.shutdown();
393         }
394         catch ( Exception e )
395         {
396         }
397 
398         sysRoot = null;
399     }
400 
401     
402     /**
403      * Imports the LDIF entries packaged with the Eve JNDI provider jar into
404      * the newly created system partition to prime it up for operation.  Note
405      * that only ou=system entries will be added - entries for other partitions
406      * cannot be imported and will blow chunks.
407      *
408      * @throws NamingException if there are problems reading the ldif file and
409      * adding those entries to the system partition
410      * @param in the input stream with the ldif
411      */
412     protected void importLdif( InputStream in ) throws NamingException
413     {
414         try
415         {
416             for ( LdifEntry ldifEntry:new LdifReader( in ) )
417             {
418                 rootDSE.add( 
419                     new DefaultServerEntry( 
420                         rootDSE.getDirectoryService().getRegistries(), ldifEntry.getEntry() ) ); 
421             }
422         }
423         catch ( Exception e )
424         {
425             String msg = "failed while trying to parse system ldif file";
426             NamingException ne = new LdapConfigurationException( msg );
427             ne.setRootCause( e );
428             throw ne;
429         }
430     }
431 
432     
433     /**
434      * Inject an ldif String into the server. DN must be relative to the
435      * root.
436      * @param ldif the entries to inject
437      * @throws NamingException if the entries cannot be added
438      */
439     protected void injectEntries( String ldif ) throws Exception
440     {
441         LdifReader reader = new LdifReader();
442         List<LdifEntry> entries = reader.parseLdif( ldif );
443 
444         for ( LdifEntry entry : entries )
445         {
446             rootDSE.add( 
447                 new DefaultServerEntry( 
448                     rootDSE.getDirectoryService().getRegistries(), entry.getEntry() ) ); 
449         }
450     }
451 }