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 }