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.operations.bind;
21  
22  
23  import java.util.HashMap;
24  import java.util.Hashtable;
25  import java.util.Map;
26  
27  import javax.naming.Context;
28  import javax.naming.NamingEnumeration;
29  import javax.naming.NamingException;
30  import javax.naming.NoPermissionException;
31  import javax.naming.OperationNotSupportedException;
32  import javax.naming.directory.Attribute;
33  import javax.naming.directory.Attributes;
34  import javax.naming.directory.BasicAttribute;
35  import javax.naming.directory.BasicAttributes;
36  import javax.naming.directory.DirContext;
37  import javax.naming.directory.InitialDirContext;
38  import javax.naming.directory.SearchControls;
39  import javax.naming.directory.SearchResult;
40  import javax.naming.ldap.InitialLdapContext;
41  
42  import org.apache.directory.server.core.DefaultDirectoryService;
43  import org.apache.directory.server.core.DirectoryService;
44  import org.apache.directory.server.core.integ.IntegrationUtils;
45  import org.apache.directory.server.core.integ.Level;
46  import org.apache.directory.server.core.integ.annotations.ApplyLdifs;
47  import org.apache.directory.server.core.integ.annotations.CleanupLevel;
48  import org.apache.directory.server.core.integ.annotations.Factory;
49  import org.apache.directory.server.integ.LdapServerFactory;
50  import org.apache.directory.server.integ.SiRunner;
51  import org.apache.directory.server.ldap.LdapService;
52  import org.apache.directory.server.ldap.handlers.bind.MechanismHandler;
53  import org.apache.directory.server.ldap.handlers.bind.SimpleMechanismHandler;
54  import org.apache.directory.server.ldap.handlers.bind.cramMD5.CramMd5MechanismHandler;
55  import org.apache.directory.server.ldap.handlers.bind.digestMD5.DigestMd5MechanismHandler;
56  import org.apache.directory.server.ldap.handlers.bind.gssapi.GssapiMechanismHandler;
57  import org.apache.directory.server.ldap.handlers.bind.ntlm.NtlmMechanismHandler;
58  import org.apache.directory.server.ldap.handlers.extended.StoredProcedureExtendedOperationHandler;
59  import org.apache.directory.server.protocol.shared.SocketAcceptor;
60  import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
61  import org.apache.directory.shared.asn1.util.Asn1StringUtils;
62  import org.apache.directory.shared.ldap.constants.SupportedSaslMechanisms;
63  import org.apache.directory.shared.ldap.message.MutableControl;
64  import org.apache.directory.shared.ldap.util.ArrayUtils;
65  import org.apache.mina.util.AvailablePortFinder;
66  import org.junit.After;
67  import org.junit.Before;
68  import org.junit.Test;
69  import org.junit.runner.RunWith;
70  
71  import static org.junit.Assert.fail;
72  import static org.junit.Assert.assertEquals;
73  import static org.junit.Assert.assertTrue;
74  import static org.junit.Assert.assertFalse;
75  import static org.junit.Assert.assertNotNull;
76  
77  
78  /**
79   * A set of miscellaneous tests.
80   *
81   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
82   * @version $Rev: 682556 $
83   */
84  @RunWith ( SiRunner.class ) 
85  @CleanupLevel ( Level.CLASS )
86  @Factory ( MiscBindIT.Factory.class )
87  @ApplyLdifs( {
88      // Entry #0
89      "dn: dc=aPache,dc=org\n" +
90      "dc: aPache\n" +
91      "objectClass: top\n" +
92      "objectClass: domain\n\n"
93      }
94  )
95  public class MiscBindIT
96  {
97      public static LdapService ldapService;
98  
99      
100     public static class Factory implements LdapServerFactory
101     {
102         public LdapService newInstance() throws Exception
103         {
104             DirectoryService service = new DefaultDirectoryService();
105             IntegrationUtils.doDelete( service.getWorkingDirectory() );
106             service.getChangeLog().setEnabled( true );
107             service.setAllowAnonymousAccess( true );
108             service.setShutdownHookEnabled( false );
109 
110             JdbmPartition apache = new JdbmPartition();
111             apache.setId( "apache" );
112 
113             // @TODO need to make this configurable for the system partition
114             apache.setCacheSize( 500 );
115             apache.setSuffix( "dc=aPache,dc=org" );
116             apache.setId( "apache" );
117             service.addPartition( apache );
118 
119             // change the working directory to something that is unique
120             // on the system and somewhere either under target directory
121             // or somewhere in a temp area of the machine.
122 
123             LdapService ldapService = new LdapService();
124             ldapService.setDirectoryService( service );
125             ldapService.setSocketAcceptor( new SocketAcceptor( null ) );
126             ldapService.setIpPort( AvailablePortFinder.getNextAvailable( 1024 ) );
127             ldapService.setAllowAnonymousAccess( true );
128             ldapService.addExtendedOperationHandler( new StoredProcedureExtendedOperationHandler() );
129 
130             // Setup SASL Mechanisms
131             
132             Map<String, MechanismHandler> mechanismHandlerMap = new HashMap<String,MechanismHandler>();
133             mechanismHandlerMap.put( SupportedSaslMechanisms.PLAIN, new SimpleMechanismHandler() );
134 
135             CramMd5MechanismHandler cramMd5MechanismHandler = new CramMd5MechanismHandler();
136             mechanismHandlerMap.put( SupportedSaslMechanisms.CRAM_MD5, cramMd5MechanismHandler );
137 
138             DigestMd5MechanismHandler digestMd5MechanismHandler = new DigestMd5MechanismHandler();
139             mechanismHandlerMap.put( SupportedSaslMechanisms.DIGEST_MD5, digestMd5MechanismHandler );
140 
141             GssapiMechanismHandler gssapiMechanismHandler = new GssapiMechanismHandler();
142             mechanismHandlerMap.put( SupportedSaslMechanisms.GSSAPI, gssapiMechanismHandler );
143 
144             NtlmMechanismHandler ntlmMechanismHandler = new NtlmMechanismHandler();
145             mechanismHandlerMap.put( SupportedSaslMechanisms.NTLM, ntlmMechanismHandler );
146             mechanismHandlerMap.put( SupportedSaslMechanisms.GSS_SPNEGO, ntlmMechanismHandler );
147 
148             ldapService.setSaslMechanismHandlers( mechanismHandlerMap );
149 
150             return ldapService;
151         }
152     }
153     
154     
155     
156     private boolean oldAnnonymousAccess;
157     
158     
159     @Before
160     public void recordAnnonymous() throws NamingException
161     {
162         oldAnnonymousAccess = ldapService.getDirectoryService().isAllowAnonymousAccess();
163     }
164     
165     
166     @After
167     public void revertAnonnymous()
168     {
169         ldapService.getDirectoryService().setAllowAnonymousAccess( oldAnnonymousAccess );
170         ldapService.setAllowAnonymousAccess( oldAnnonymousAccess );
171     }
172 
173     
174     /**
175      * Test to make sure anonymous binds are disabled when going through
176      * the wire protocol.
177      *
178      * @throws Exception if anything goes wrong
179      */
180     @Test
181     public void testDisableAnonymousBinds() throws Exception
182     {
183         ldapService.getDirectoryService().setAllowAnonymousAccess( false );
184         ldapService.setAllowAnonymousAccess( false );
185         
186         // Use the SUN JNDI provider to hit server port and bind as anonymous
187         InitialDirContext ic = null;
188         final Hashtable<String, Object> env = new Hashtable<String, Object>();
189 
190         env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() + "/ou=system" );
191         env.put( Context.SECURITY_AUTHENTICATION, "none" );
192         env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
193 
194         boolean connected = false;
195         while ( !connected )
196         {
197             try
198             {
199                 ic = new InitialDirContext( env );
200                 connected = true;
201             }
202             catch ( Exception e )
203             {
204             	// We should not get here
205             	fail();
206             }
207         }
208 
209         ldapService.getDirectoryService().setAllowAnonymousAccess( false );
210         
211         try
212         {
213             ic.search( "", "(objectClass=*)", new SearchControls() );
214             fail( "If anonymous binds are disabled we should never get here!" );
215         }
216         catch ( NoPermissionException e )
217         {
218         }
219 
220         Attributes attrs = new BasicAttributes( true );
221         Attribute oc = new BasicAttribute( "objectClass" );
222         attrs.put( oc );
223         oc.add( "top" );
224         oc.add( "organizationalUnit" );
225 
226         try
227         {
228             ic.createSubcontext( "ou=blah", attrs );
229         }
230         catch ( NoPermissionException e )
231         {
232         }
233     }
234 
235 
236     /**
237      * Test to make sure anonymous binds are allowed on the RootDSE even when disabled
238      * in general when going through the wire protocol.
239      *
240      * @throws Exception if anything goes wrong
241      */
242     @Test
243     public void testEnableAnonymousBindsOnRootDSE() throws Exception
244     {
245         ldapService.getDirectoryService().setAllowAnonymousAccess( true );
246 
247         // Use the SUN JNDI provider to hit server port and bind as anonymous
248         Hashtable<String, Object> env = new Hashtable<String, Object>();
249 
250         env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() + "/" );
251         env.put( Context.SECURITY_AUTHENTICATION, "none" );
252         env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
253 
254         InitialDirContext ctx = new InitialDirContext( env );
255         SearchControls cons = new SearchControls();
256         cons.setSearchScope( SearchControls.OBJECT_SCOPE );
257         NamingEnumeration<SearchResult> list = ctx.search( "", "(objectClass=*)", cons );
258         
259         SearchResult result = null;
260         
261         if ( list.hasMore() )
262         {
263             result = list.next();
264         }
265         
266         assertFalse( list.hasMore() );
267         list.close();
268 
269         assertNotNull( result );
270         assertEquals( "", result.getName().trim() );
271     }
272 
273 
274     /**
275      * Test to make sure that if anonymous binds are allowed a user may search
276      * within a a partition.
277      *
278      * @throws Exception if anything goes wrong
279      */
280     @Test
281     public void testAnonymousBindsEnabledBaseSearch() throws Exception
282     {
283         ldapService.getDirectoryService().setAllowAnonymousAccess( true );
284 
285         // Use the SUN JNDI provider to hit server port and bind as anonymous
286         Hashtable<String, Object> env = new Hashtable<String, Object>();
287 
288         env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() + "/" );
289         env.put( Context.SECURITY_AUTHENTICATION, "none" );
290         env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
291 
292         InitialDirContext ctx = new InitialDirContext( env );
293         SearchControls cons = new SearchControls();
294         cons.setSearchScope( SearchControls.OBJECT_SCOPE );
295         NamingEnumeration<SearchResult> list = ctx.search( "dc=apache,dc=org", "(objectClass=*)", cons );
296         SearchResult result = null;
297         
298         if ( list.hasMore() )
299         {
300             result = list.next();
301         }
302         
303         assertFalse( list.hasMore() );
304         list.close();
305 
306         assertNotNull( result );
307         assertNotNull( result.getAttributes().get( "dc" ) );
308     }
309 
310 
311     /**
312      * Reproduces the problem with
313      * <a href="http://issues.apache.org/jira/browse/DIREVE-239">DIREVE-239</a>.
314      *
315      * @throws Exception if anything goes wrong
316      */
317     @Test
318     public void testAdminAccessBug() throws Exception
319     {
320         ldapService.getDirectoryService().setAllowAnonymousAccess( true );
321 
322         // Use the SUN JNDI provider to hit server port and bind as anonymous
323 
324         final Hashtable<String, Object> env = new Hashtable<String, Object>();
325 
326         env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
327         env.put( "java.naming.ldap.version", "3" );
328         env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
329 
330         Attributes attributes = new BasicAttributes( true );
331         Attribute objectClass = new BasicAttribute( "objectClass" );
332         objectClass.add( "top" );
333         objectClass.add( "organizationalUnit" );
334         attributes.put( objectClass );
335         attributes.put( "ou", "blah" );
336         InitialDirContext ctx = new InitialDirContext( env );
337         ctx.createSubcontext( "ou=blah,ou=system", attributes );
338         SearchControls controls = new SearchControls();
339         controls.setSearchScope( SearchControls.OBJECT_SCOPE );
340         controls.setReturningAttributes( new String[]
341                 {"+"} );
342         NamingEnumeration<SearchResult> list = ctx.search( "ou=blah,ou=system", "(objectClass=*)", controls );
343         SearchResult result = list.next();
344         list.close();
345         Attribute creatorsName = result.getAttributes().get( "creatorsName" );
346         assertEquals( "", creatorsName.get() );
347     }
348 
349 
350     /**
351      * Test case for <a href="http://issues.apache.org/jira/browse/DIREVE-284" where users in
352      * mixed case partitions were not able to authenticate properly.  This test case creates
353      * a new partition under dc=aPache,dc=org, it then creates the example user in the JIRA
354      * issue and attempts to authenticate as that user.
355      *
356      * @throws Exception if the user cannot authenticate or test fails
357      */
358     @Test
359     public void testUserAuthOnMixedCaseSuffix() throws Exception
360     {
361         ldapService.getDirectoryService().setAllowAnonymousAccess( true );
362 
363         Hashtable<String, Object> env = new Hashtable<String, Object>();
364 
365         env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() + "/dc=aPache,dc=org" );
366         env.put( "java.naming.ldap.version", "3" );
367         env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
368         InitialDirContext ctx = new InitialDirContext( env );
369         Attributes attrs = ctx.getAttributes( "" );
370         assertTrue( attrs.get( "dc" ).get().equals( "aPache" ) );
371 
372         Attributes user = new BasicAttributes( "cn", "Kate Bush", true );
373         Attribute oc = new BasicAttribute( "objectClass" );
374         oc.add( "top" );
375         oc.add( "person" );
376         oc.add( "organizationalPerson" );
377         oc.add( "inetOrgPerson" );
378         user.put( oc );
379         user.put( "sn", "Bush" );
380         user.put( "userPassword", "Aerial" );
381         ctx.createSubcontext( "cn=Kate Bush", user );
382 
383         env.put( Context.SECURITY_AUTHENTICATION, "simple" );
384         env.put( Context.SECURITY_CREDENTIALS, "Aerial" );
385         env.put( Context.SECURITY_PRINCIPAL, "cn=Kate Bush,dc=aPache,dc=org" );
386 
387         InitialDirContext userCtx = new InitialDirContext( env );
388         assertNotNull( userCtx );
389     }
390 
391 
392     @Test
393     public void testFailureWithUnsupportedControl() throws Exception
394     {
395         MutableControl unsupported = new MutableControl()
396         {
397             boolean isCritical = true;
398             private static final long serialVersionUID = 1L;
399 
400 
401             @SuppressWarnings("unused")
402             public String getType()
403             {
404                 return "1.1.1.1";
405             }
406 
407 
408             public void setID( String oid )
409             {
410             }
411 
412 
413             @SuppressWarnings("unused")
414             public byte[] getValue()
415             {
416                 return new byte[0];
417             }
418 
419 
420             @SuppressWarnings("unused")
421             public void setValue( byte[] value )
422             {
423             }
424 
425 
426             public boolean isCritical()
427             {
428                 return isCritical;
429             }
430 
431 
432             public void setCritical( boolean isCritical )
433             {
434                 this.isCritical = isCritical;
435             }
436 
437 
438             public String getID()
439             {
440                 return "1.1.1.1";
441             }
442 
443 
444             public byte[] getEncodedValue()
445             {
446                 return new byte[0];
447             }
448         };
449         
450         ldapService.getDirectoryService().setAllowAnonymousAccess( true );
451         
452         Hashtable<String, Object> env = new Hashtable<String, Object>();
453 
454         env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() + "/ou=system" );
455         env.put( "java.naming.ldap.version", "3" );
456         env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
457         env.put( Context.SECURITY_AUTHENTICATION, "simple" );
458         env.put( Context.SECURITY_CREDENTIALS, "secret" );
459         env.put( Context.SECURITY_PRINCIPAL, "uid=admin,ou=system" );
460         InitialLdapContext ctx = new InitialLdapContext( env, null );
461 
462         Attributes user = new BasicAttributes( "cn", "Kate Bush", true );
463         Attribute oc = new BasicAttribute( "objectClass" );
464         oc.add( "top" );
465         oc.add( "person" );
466         oc.add( "organizationalPerson" );
467         oc.add( "inetOrgPerson" );
468         user.put( oc );
469         user.put( "sn", "Bush" );
470         user.put( "userPassword", "Aerial" );
471         ctx.setRequestControls( new MutableControl[]
472                 {unsupported} );
473 
474         try
475         {
476             ctx.createSubcontext( "cn=Kate Bush", user );
477         }
478         catch ( OperationNotSupportedException e )
479         {
480         }
481 
482         unsupported.setCritical( false );
483         DirContext kate = ctx.createSubcontext( "cn=Kate Bush", user );
484         assertNotNull( kate );
485         assertTrue( ArrayUtils.isEquals( Asn1StringUtils.getBytesUtf8( "Aerial" ), kate.getAttributes( "" ).get(
486                 "userPassword" ).get() ) );
487     }
488 }