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.search;
21  
22  
23  import java.util.Arrays;
24  import java.util.HashSet;
25  import java.util.Set;
26  
27  import javax.naming.InvalidNameException;
28  import javax.naming.NameNotFoundException;
29  import javax.naming.NamingEnumeration;
30  import javax.naming.NamingException;
31  import javax.naming.directory.Attribute;
32  import javax.naming.directory.Attributes;
33  import javax.naming.directory.BasicAttribute;
34  import javax.naming.directory.BasicAttributes;
35  import javax.naming.directory.DirContext;
36  import javax.naming.directory.SearchControls;
37  import javax.naming.directory.SearchResult;
38  import javax.naming.ldap.Control;
39  import javax.naming.ldap.LdapContext;
40  
41  import org.apache.directory.server.core.integ.Level;
42  import org.apache.directory.server.core.integ.annotations.ApplyLdifs;
43  import org.apache.directory.server.core.integ.annotations.CleanupLevel;
44  import org.apache.directory.server.core.subtree.SubentryInterceptor;
45  import org.apache.directory.server.integ.SiRunner;
46  import org.apache.directory.server.ldap.LdapService;
47  
48  import static org.apache.directory.server.integ.ServerIntegrationUtils.getWiredContext;
49  
50  import org.apache.directory.shared.ldap.constants.SchemaConstants;
51  import org.apache.directory.shared.ldap.message.SubentriesControl;
52  import org.junit.Test;
53  import org.junit.runner.RunWith;
54  import static org.junit.Assert.fail;
55  import static org.junit.Assert.assertNull;
56  import static org.junit.Assert.assertTrue;
57  import static org.junit.Assert.assertFalse;
58  import static org.junit.Assert.assertEquals;
59  import static org.junit.Assert.assertNotNull;
60  
61  
62  /**
63   * Testcase with different modify operations on a person entry. Each includes a
64   * single add op only. Created to demonstrate DIREVE-241 ("Adding an already
65   * existing attribute value with a modify operation does not cause an error.").
66   * 
67   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
68   * @version $Rev: 682556 $
69   */
70  @RunWith ( SiRunner.class ) 
71  @CleanupLevel ( Level.CLASS )
72  @ApplyLdifs( {
73      
74      // Entry # 0
75      "dn: cn=Kate Bush,ou=system\n" +
76      "objectClass: person\n" +
77      "objectClass: organizationalPerson\n" +
78      "objectClass: inetOrgPerson\n" +
79      "objectClass: strongAuthenticationUser\n" +
80      "objectClass: top\n" +
81      "userCertificate:: NFZOXw==\n" +
82      "cn: Kate Bush\n" +
83      "description: this is a person\n" +
84      "sn: Bush\n" +
85      "jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD//gAX\n" +
86      " Q3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAQCwwODAoQDg0OEhEQExgoGhgWFhgxIyUdKDozPTw\n" +
87      " 5Mzg3QEhcTkBEV0U3OFBtUVdfYmdoZz5NcXlwZHhcZWdj/9sAQwEREhIYFRgvGhovY0I4QmNjY2\n" +
88      " NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Nj/8AAEQgAAQABA\n" +
89      " wEiAAIRAQMRAf/EABUAAQEAAAAAAAAAAAAAAAAAAAAF/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/E\n" +
90      " ABUBAQEAAAAAAAAAAAAAAAAAAAUG/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8\n" +
91      " AigC14//Z\n\n" +
92      
93      // Entry # 2
94      "dn: cn=Tori Amos,ou=system\n" +
95      "objectClass: person\n" +
96      "objectClass: organizationalPerson\n" +
97      "objectClass: inetOrgPerson\n" +
98      "objectClass: strongAuthenticationUser\n" +
99      "objectClass: top\n" +
100     "userCertificate:: NFZOXw==\n" +
101     "cn: Tori Amos\n" +
102     "description: an American singer-songwriter\n" +
103     "sn: Amos\n" +
104     "jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD//gAX\n" +
105     " Q3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAQCwwODAoQDg0OEhEQExgoGhgWFhgxIyUdKDozPTw\n" +
106     " 5Mzg3QEhcTkBEV0U3OFBtUVdfYmdoZz5NcXlwZHhcZWdj/9sAQwEREhIYFRgvGhovY0I4QmNjY2\n" +
107     " NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Nj/8AAEQgAAQABA\n" +
108     " wEiAAIRAQMRAf/EABUAAQEAAAAAAAAAAAAAAAAAAAAF/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/E\n" +
109     " ABUBAQEAAAAAAAAAAAAAAAAAAAUG/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8\n" +
110     " AigC14//Z\n\n" + 
111 
112     // Entry # 3
113     "dn: cn=Rolling-Stones,ou=system\n" +
114     "objectClass: person\n" +
115     "objectClass: organizationalPerson\n" +
116     "objectClass: inetOrgPerson\n" +
117     "objectClass: strongAuthenticationUser\n" +
118     "objectClass: top\n" +
119     "userCertificate:: NFZOXw==\n" +
120     "cn: Rolling-Stones\n" +
121     "description: an English singer-songwriter\n" +
122     "sn: Jagger\n" +
123     "jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD//gAX\n" +
124     " Q3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAQCwwODAoQDg0OEhEQExgoGhgWFhgxIyUdKDozPTw\n" +
125     " 5Mzg3QEhcTkBEV0U3OFBtUVdfYmdoZz5NcXlwZHhcZWdj/9sAQwEREhIYFRgvGhovY0I4QmNjY2\n" +
126     " NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Nj/8AAEQgAAQABA\n" +
127     " wEiAAIRAQMRAf/EABUAAQEAAAAAAAAAAAAAAAAAAAAF/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/E\n" +
128     " ABUBAQEAAAAAAAAAAAAAAAAAAAUG/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8\n" +
129     " AigC14//Z\n\n" + 
130 
131     // Entry # 4
132     "dn: cn=Heather Nova,ou=system\n" +
133     "objectClass: person\n" +
134     "objectClass: organizationalPerson\n" +
135     "objectClass: inetOrgPerson\n" +
136     "objectClass: strongAuthenticationUser\n" +
137     "objectClass: top\n" +
138     "userCertificate:: NFZOXw==\n" +
139     "cn: Heather Nova\n" +
140     "sn: Nova\n" +
141     "jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD//gAX\n" +
142     " Q3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAQCwwODAoQDg0OEhEQExgoGhgWFhgxIyUdKDozPTw\n" +
143     " 5Mzg3QEhcTkBEV0U3OFBtUVdfYmdoZz5NcXlwZHhcZWdj/9sAQwEREhIYFRgvGhovY0I4QmNjY2\n" +
144     " NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Nj/8AAEQgAAQABA\n" +
145     " wEiAAIRAQMRAf/EABUAAQEAAAAAAAAAAAAAAAAAAAAF/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/E\n" +
146     " ABUBAQEAAAAAAAAAAAAAAAAAAAUG/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8\n" +
147     " AigC14//Z\n\n" +
148     
149     // Entry #5
150     "dn: cn=Janis Joplin,ou=system\n" +
151     "objectClass: person\n" +
152     "objectClass: organizationalPerson\n" +
153     "objectClass: inetOrgPerson\n" +
154     "objectClass: top\n" +
155     "objectClass: strongAuthenticationUser\n" +
156     "cn: Janis Joplin\n" +
157     "sn: Joplin\n" +
158     "userCertificate:: \n\n" +
159     
160     // Entry #6
161     "dn: cn=Kim Wilde,ou=system\n" +
162     "objectClass: person\n" +
163     "objectClass: top\n" +
164     "cn: Kim Wilde\n" +
165     "sn: Wilde\n\n"
166     }
167 )
168 public class SearchIT 
169 {
170     private static final String BASE = "ou=system";
171     
172     public static LdapService ldapService;
173     
174     private static final String RDN = "cn=Tori Amos";
175     private static final String RDN2 = "cn=Rolling-Stones";
176     private static final String HEATHER_RDN = "cn=Heather Nova";
177     private static final String FILTER = "(objectclass=*)";
178 
179 
180     private static final byte[] JPEG = new byte[]
181         {
182             (byte)0xff, (byte)0xd8, (byte)0xff, (byte)0xe0, 0x00, 0x10, 0x4a, 0x46, 
183             0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48,
184             0x00, 0x48, 0x00, 0x00, (byte)0xff, (byte)0xe1, 0x00, 0x16, 
185             0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d,
186             0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 
187             0x00, 0x00, 0x00, 0x00, (byte)0xff, (byte)0xfe, 0x00, 0x17,
188             0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 
189             0x77, 0x69, 0x74, 0x68, 0x20, 0x54, 0x68, 0x65,
190             0x20, 0x47, 0x49, 0x4d, 0x50, (byte)0xff, (byte)0xdb, 0x00, 
191             0x43, 0x00, 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a,
192             0x10, 0x0e, 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 
193             0x18, 0x28, 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31,
194             0x23, 0x25, 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 
195             0x39, 0x33, 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e,
196             0x40, 0x44, 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 
197             0x51, 0x57, 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e,
198             0x4d, 0x71, 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 
199             0x67, 0x63, (byte)0xff, (byte)0xdb, 0x00, 0x43, 0x01, 0x11,
200             0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, 0x1a, 
201             0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, 0x63,
202             0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 
203             0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
204             0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 
205             0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
206             0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 
207             0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, (byte)0xff,
208             (byte)0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01, 
209             0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03,
210             0x11, 0x01, (byte)0xff, (byte)0xc4, 0x00, 0x15, 0x00, 0x01, 
211             0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
212             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
213             0x05, (byte)0xff, (byte)0xc4, 0x00, 0x14, 0x10, 0x01, 0x00,
214             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
215             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0xff,
216             (byte)0xc4, 0x00, 0x15, 0x01, 0x01, 0x01, 0x00, 0x00, 
217             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
218             0x00, 0x00, 0x00, 0x00, 0x05, 0x06, (byte)0xff, (byte)0xc4, 
219             0x00, 0x14, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00,
220             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
221             0x00, 0x00, 0x00, 0x00, (byte)0xff, (byte)0xda, 0x00, 0x0c,
222             0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 
223             0x3f, 0x00, (byte)0x8a, 0x00, (byte)0xb5, (byte)0xe3, (byte)0xff, (byte)0xd9,
224         };
225                                             
226 
227 
228     /**
229      * Creation of required attributes of a person entry.
230      */
231     private Attributes getPersonAttributes( String sn, String cn )
232     {
233         Attributes attributes = new BasicAttributes( true );
234         Attribute attribute = new BasicAttribute( "objectClass" );
235         attribute.add( "top" );
236         attribute.add( "person" );
237         attribute.add( "organizationalPerson" );
238         attribute.add( "inetOrgPerson" );
239         attributes.put( attribute );
240         attributes.put( "cn", cn );
241         attributes.put( "sn", sn );
242         attributes.put( "jpegPhoto", JPEG );
243 
244         return attributes;
245     }
246 
247 
248     private void checkForAttributes( Attributes attrs, String[] attrNames )
249     {
250         for ( String attrName : attrNames )
251         {
252             assertNotNull( "Check if attr " + attrName + " is present", attrs.get( attrName ) );
253         }
254     }
255 
256 
257     /**
258      * For DIRSERVER-715 and part of DIRSERVER-169.  May include other tests
259      * for binary attribute based searching.
260      */
261     @Test
262     public void testSearchByBinaryAttribute() throws Exception 
263     {
264         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
265         byte[] certData = new byte[] { 0x34, 0x56, 0x4e, 0x5f };
266         
267         // Search for kate by cn first
268         SearchControls controls = new SearchControls();
269         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
270         NamingEnumeration<SearchResult> enm = ctx.search( "", "(cn=Kate Bush)", controls );
271         assertTrue( enm.hasMore() );
272         SearchResult sr = enm.next();
273         assertNotNull( sr );
274         assertFalse( enm.hasMore() );
275         assertEquals( "cn=Kate Bush", sr.getName() );
276 
277         // TODO enable this test here
278         // Failing here below this due to the frontend interpretting the byte[]
279         // as a String value.  I see the value sent to the SearchHandler of the
280         // filter to be a String looking like: "[B@17210a5".
281         
282         enm = ctx.search( "", "(&(cn=Kate Bush)(userCertificate={0}))", new Object[] {certData}, controls );
283         assertTrue( enm.hasMore() );
284         sr = ( SearchResult ) enm.next();
285         assertNotNull( sr );
286         assertFalse( enm.hasMore() );
287         assertEquals( "cn=Kate Bush", sr.getName() );
288     }
289 
290     
291     @Test
292     public void testSearch() throws Exception
293     {
294         LdapContext ctx = getWiredContext( ldapService );
295         SearchControls controls = new SearchControls();
296         controls.setSearchScope( SearchControls.OBJECT_SCOPE );
297         controls.setTimeLimit( 10 );
298         
299         try 
300         {
301             ctx.search( "myBadDN", "(objectClass=*)", controls );
302 
303             fail(); // We should get an exception here
304         } 
305         catch ( InvalidNameException ine ) 
306         {
307             // Expected.
308         } 
309         catch ( NamingException ne )
310         {
311             fail();
312         }
313         catch( Exception e )
314         {
315             fail();
316         }
317         
318         try
319         {
320             controls = new SearchControls();
321             controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
322             controls.setTimeLimit( 10 );
323             
324             NamingEnumeration<SearchResult> result = ctx.search( "ou=system", "(objectClass=*)", controls );
325 
326             assertTrue( result.hasMore() ); 
327         } 
328         catch ( InvalidNameException ine ) 
329         {
330             fail();
331             // Expected.
332         } 
333         catch ( NamingException ne )
334         {
335             fail();
336         }
337     }
338 
339     
340     /**
341      * Performs a single level search from ou=system base and 
342      * returns the set of DNs found.
343      */
344     private Set<String> search( String filter ) throws Exception
345     {
346         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
347         SearchControls controls = new SearchControls();
348         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
349         NamingEnumeration<SearchResult> ii = ctx.search( "", filter, controls );
350         
351         // collect all results 
352         HashSet<String> results = new HashSet<String>();
353         while ( ii.hasMore() )
354         {
355             SearchResult result = ii.next();
356             results.add( result.getName() );
357         }
358         
359         return results;
360     }
361 
362     
363     @Test
364     public void testDirserver635() throws Exception
365     {
366         // -------------------------------------------------------------------
367         Set<String> results = search( "(|(cn=Kate*)(cn=Tori*))" );
368         assertEquals( "returned size of results", 2, results.size() );
369         assertTrue( "contains cn=Tori Amos", results.contains( "cn=Tori Amos" ) );
370         assertTrue( "contains cn=Kate Bush", results.contains( "cn=Kate Bush" ) );
371 
372         // -------------------------------------------------------------------
373         results = search( "(|(cn=*Amos)(cn=Kate*))" );
374         assertEquals( "returned size of results", 2, results.size() );
375         assertTrue( "contains cn=Tori Amos", results.contains( "cn=Tori Amos" ) );
376         assertTrue( "contains cn=Kate Bush", results.contains( "cn=Kate Bush" ) );
377 
378         // -------------------------------------------------------------------
379         results = search( "(|(cn=Kate Bush)(cn=Tori*))" );
380         assertEquals( "returned size of results", 2, results.size() );
381         assertTrue( "contains cn=Tori Amos", results.contains( "cn=Tori Amos" ) );
382         assertTrue( "contains cn=Kate Bush", results.contains( "cn=Kate Bush" ) );
383 
384         // -------------------------------------------------------------------
385         results = search( "(|(cn=*Amos))" );
386         assertEquals( "returned size of results", 1, results.size() );
387         assertTrue( "contains cn=Tori Amos", results.contains( "cn=Tori Amos" ) );
388     }
389 
390     
391     /**
392      * Search operation with a base DN which contains a BER encoded value.
393      */
394     //@Test
395     /*public void testSearchBEREncodedBase() throws NamingException
396     {
397         // create additional entry
398         Attributes attributes = this.getPersonAttributes( "Ferry", "Bryan Ferry" );
399         ctx.createSubcontext( "sn=Ferry", attributes );
400 
401         SearchControls sctls = new SearchControls();
402         sctls.setSearchScope( SearchControls.OBJECT_SCOPE );
403         String FILTER = "(cn=Bryan Ferry)";
404 
405         // sn=Ferry with BEROctetString values
406         String base = "2.5.4.4=#4665727279";
407 
408         try
409         {
410             // Check entry
411             NamingEnumeration enm = ctx.search( base, FILTER, sctls );
412             assertTrue( enm.hasMore() );
413             while ( enm.hasMore() )
414             {
415                 SearchResult sr = ( SearchResult ) enm.next();
416                 Attributes attrs = sr.getObject();
417                 Attribute sn = attrs.get( "sn" );
418                 assertNotNull( sn );
419                 assertTrue( sn.contains( "Ferry" ) );
420             }
421         }
422         catch ( Exception e )
423         {
424             fail( e.getMessage() );
425         }
426     }*/
427 
428     
429     /**
430      * Search operation with a base DN which contains a BER encoded value.
431      */
432     @Test
433     public void testSearchWithBackslashEscapedBase() throws Exception
434     {
435         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
436 
437         // create additional entry
438         Attributes attributes = this.getPersonAttributes( "Ferry", "Bryan Ferry" );
439         ctx.createSubcontext( "sn=Ferry", attributes );
440 
441         SearchControls sctls = new SearchControls();
442         sctls.setSearchScope( SearchControls.OBJECT_SCOPE );
443         String filter = "(cn=Bryan Ferry)";
444 
445         // sn=Ferry with BEROctetString values
446         String base = "sn=\\46\\65\\72\\72\\79";
447 
448         try
449         {
450             // Check entry
451             NamingEnumeration<SearchResult> enm = ctx.search( base, filter, sctls );
452             assertTrue( enm.hasMore() );
453             while ( enm.hasMore() )
454             {
455                 SearchResult sr = enm.next();
456                 Attributes attrs = sr.getAttributes();
457                 Attribute sn = attrs.get( "sn" );
458                 assertNotNull( sn );
459                 assertTrue( sn.contains( "Ferry" ) );
460             }
461         }
462         catch ( Exception e )
463         {
464             fail( e.getMessage() );
465         }
466     }
467 
468     
469     /**
470      * Add a new attribute to a person entry.
471      * 
472      * @throws NamingException
473      */
474     @Test
475     public void testSearchValue() throws Exception
476     {
477         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
478 
479         // Setting up search controls for compare op
480         SearchControls ctls = new SearchControls();
481         ctls.setReturningAttributes( new String[]
482             { "*" } ); // no attributes
483         ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
484 
485         // Search for all entries
486         NamingEnumeration<SearchResult> results = ctx.search( RDN, "(cn=*)", ctls );
487         assertTrue( results.hasMore() );
488 
489         results = ctx.search( RDN2, "(cn=*)", ctls );
490         assertTrue( results.hasMore() );
491 
492         // Search for all entries ending by Amos
493         results = ctx.search( RDN, "(cn=*Amos)", ctls );
494         assertTrue( results.hasMore() );
495 
496         results = ctx.search( RDN2, "(cn=*Amos)", ctls );
497         assertFalse( results.hasMore() );
498 
499         // Search for all entries ending by amos
500         results = ctx.search( RDN, "(cn=*amos)", ctls );
501         assertTrue( results.hasMore() );
502 
503         results = ctx.search( RDN2, "(cn=*amos)", ctls );
504         assertFalse( results.hasMore() );
505 
506         // Search for all entries starting by Tori
507         results = ctx.search( RDN, "(cn=Tori*)", ctls );
508         assertTrue( results.hasMore() );
509 
510         results = ctx.search( RDN2, "(cn=Tori*)", ctls );
511         assertFalse( results.hasMore() );
512 
513         // Search for all entries starting by tori
514         results = ctx.search( RDN, "(cn=tori*)", ctls );
515         assertTrue( results.hasMore() );
516 
517         results = ctx.search( RDN2, "(cn=tori*)", ctls );
518         assertFalse( results.hasMore() );
519 
520         // Search for all entries containing ori
521         results = ctx.search( RDN, "(cn=*ori*)", ctls );
522         assertTrue( results.hasMore() );
523 
524         results = ctx.search( RDN2, "(cn=*ori*)", ctls );
525         assertFalse( results.hasMore() );
526 
527         // Search for all entries containing o and i
528         results = ctx.search( RDN, "(cn=*o*i*)", ctls );
529         assertTrue( results.hasMore() );
530 
531         results = ctx.search( RDN2, "(cn=*o*i*)", ctls );
532         assertTrue( results.hasMore() );
533 
534         // Search for all entries containing o, space and o
535         results = ctx.search( RDN, "(cn=*o* *o*)", ctls );
536         assertTrue( results.hasMore() );
537 
538         results = ctx.search( RDN2, "(cn=*o* *o*)", ctls );
539         assertFalse( results.hasMore() );
540 
541         results = ctx.search( RDN2, "(cn=*o*-*o*)", ctls );
542         assertTrue( results.hasMore() );
543 
544         // Search for all entries starting by To and containing A
545         results = ctx.search( RDN, "(cn=To*A*)", ctls );
546         assertTrue( results.hasMore() );
547 
548         results = ctx.search( RDN2, "(cn=To*A*)", ctls );
549         assertFalse( results.hasMore() );
550 
551         // Search for all entries ending by os and containing ri
552         results = ctx.search( RDN, "(cn=*ri*os)", ctls );
553         assertTrue( results.hasMore() );
554 
555         results = ctx.search( RDN2, "(cn=*ri*os)", ctls );
556         assertFalse( results.hasMore() );
557     }
558     
559     /**
560      * Search operation with a base DN with quotes
561      *
562     @Test
563     public void testSearchWithQuotesInBase() throws NamingException {
564 
565         SearchControls sctls = new SearchControls();
566         sctls.setSearchScope(SearchControls.OBJECT_SCOPE);
567         String filter = "(cn=Tori Amos)";
568 
569         // cn="Tori Amos" (with quotes)
570         String base = "cn=\"Tori Amos\"";
571 
572         try {
573             // Check entry
574             NamingEnumeration<SearchResult> enm = ctx.search( base, filter, sctls );
575             assertTrue( enm.hasMore() );
576             
577             while ( enm.hasMore() ) {
578                 SearchResult sr = enm.next();
579                 Attributes attrs = sr.getAttributes();
580                 Attribute sn = attrs.get("sn");
581                 assertNotNull(sn);
582                 assertTrue( sn.contains( "Amos" ) );
583             }
584         } catch (Exception e) {
585             fail( e.getMessage() );
586         }
587     }
588  
589     
590     /**
591      * Tests for <a href="http://issues.apache.org/jira/browse/DIRSERVER-645">
592      * DIRSERVER-645<\a>: Wrong search FILTER evaluation with AND
593      * operator and undefined operands.
594      */
595     @Test
596     public void testUndefinedAvaInBranchFilters() throws Exception
597     {
598         // -------------------------------------------------------------------
599         Set<String> results = search( "(|(sn=Bush)(numberOfOctaves=4))" );
600         assertEquals( "returned size of results", 1, results.size() );
601         assertTrue( "contains cn=Kate Bush", results.contains( "cn=Kate Bush" ) );
602 
603         // if numberOfOctaves is undefined then this whole FILTER is undefined
604         results = search( "(&(sn=Bush)(numberOfOctaves=4))" );
605         assertEquals( "returned size of results", 0, results.size() );
606     }
607     
608     
609     @Test
610     public void testSearchSchema() throws Exception
611     {
612         SearchControls controls = new SearchControls();
613         controls.setSearchScope( SearchControls.OBJECT_SCOPE );
614         controls.setReturningAttributes( new String[] { "objectClasses" } );
615 
616         LdapContext ctx = getWiredContext( ldapService );
617 
618         NamingEnumeration<SearchResult> results = ctx.search( "cn=schema", "objectClass=subschema", controls );
619         assertTrue( results.hasMore() );
620         SearchResult result = results.next();
621         assertNotNull( result );
622         assertFalse( results.hasMore() );
623         
624         NamingEnumeration<? extends Attribute> attrs = result.getAttributes().getAll();
625         
626         while ( attrs.hasMoreElements() )
627         {
628             Attribute attr = ( Attribute ) attrs.next();
629             String ID = attr.getID();
630             assertEquals( "objectClasses", ID );
631         }
632         
633         assertNotNull( result.getAttributes().get( "objectClasses" ) );
634         assertEquals( 1, result.getAttributes().size() );
635     }
636     
637     
638     /**
639      * Creates an access control subentry under ou=system whose subtree covers
640      * the entire naming context.
641      *
642      * @param cn the common name and rdn for the subentry
643      * @param subtree the subtreeSpecification for the subentry
644      * @param aciItem the prescriptive ACI attribute value
645      * @throws NamingException if there is a problem creating the subentry
646      */
647     private void createAccessControlSubentry( String cn, String subtree, String aciItem ) throws Exception
648     {
649         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
650 
651         DirContext adminCtx = ctx;
652 
653         // modify ou=system to be an AP for an A/C AA if it is not already
654         Attributes ap = adminCtx.getAttributes( "", new String[] { "administrativeRole" } );
655         Attribute administrativeRole = ap.get( "administrativeRole" );
656         if ( administrativeRole == null || !administrativeRole.contains( SubentryInterceptor.AC_AREA ) )
657         {
658             Attributes changes = new BasicAttributes( "administrativeRole", SubentryInterceptor.AC_AREA, true );
659             adminCtx.modifyAttributes( "", DirContext.ADD_ATTRIBUTE, changes );
660         }
661 
662         // now add the A/C subentry below ou=system
663         Attributes subentry = new BasicAttributes( "cn", cn, true );
664         Attribute objectClass = new BasicAttribute( "objectClass" );
665         subentry.put( objectClass );
666         objectClass.add( "top" );
667         objectClass.add( SchemaConstants.SUBENTRY_OC );
668         objectClass.add( "accessControlSubentry" );
669         subentry.put( "subtreeSpecification", subtree );
670         subentry.put( "prescriptiveACI", aciItem );
671         adminCtx.createSubcontext( "cn=" + cn, subentry );
672     }
673     
674 
675     /**
676      * Test case to demonstrate DIRSERVER-705 ("object class top missing in search
677      * result, if scope is base and attribute objectClass is requested explicitly").
678      */
679     @Test
680     public void testAddWithObjectclasses() throws Exception
681     {
682         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
683 
684         SearchControls ctls = new SearchControls();
685         ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
686         ctls.setReturningAttributes( new String[]
687             { "objectclass" } );
688         String filter = "(objectclass=*)";
689         String rdn = "cn=Kim Wilde";
690 
691         NamingEnumeration<SearchResult> result = ctx.search( rdn, filter, ctls );
692         
693         if ( result.hasMore() )
694         {
695             SearchResult entry = result.next();
696             Attributes heatherReloaded = entry.getAttributes();
697             Attribute loadedOcls = heatherReloaded.get( "objectClass" );
698             assertNotNull( loadedOcls );
699             assertTrue( loadedOcls.contains( "person" ) );
700             assertTrue( loadedOcls.contains( "top" ) );
701         }
702         else
703         {
704             fail( "entry " + rdn + " not found" );
705         }
706 
707         ctx.destroySubcontext( rdn );
708     }
709 
710 
711     /**
712      * Test case to demonstrate DIRSERVER-705 ("object class top missing in search
713      * result, if scope is base and attribute objectClass is requested explicitly").
714      */
715     @Test
716     public void testAddWithMissingObjectclasses() throws Exception
717     {
718         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
719 
720         String rdn = "cn=Kate Bush";
721         SearchControls ctls = new SearchControls();
722         ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
723         ctls.setReturningAttributes( new String[]
724             { "objectclass" } );
725         String filter = "(objectclass=*)";
726 
727         NamingEnumeration<SearchResult> result = ctx.search( rdn, filter, ctls );
728         if ( result.hasMore() )
729         {
730             SearchResult entry = result.next();
731             Attributes kateReloaded = entry.getAttributes();
732             Attribute loadedOcls = kateReloaded.get( "objectClass" );
733             assertNotNull( loadedOcls );
734             assertTrue( loadedOcls.contains( "top" ) );
735             assertTrue( loadedOcls.contains( "person" ) );
736             assertTrue( loadedOcls.contains( "organizationalPerson" ) );
737 
738         }
739         else
740         {
741             fail( "entry " + rdn + " not found" );
742         }
743 
744         ctx.destroySubcontext( rdn );
745     }
746 
747 
748     @Test
749     public void testSubentryControl() throws Exception
750     {
751         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
752 
753         // create a real access control subentry
754         createAccessControlSubentry( 
755             "anyBodyAdd", 
756             "{}", 
757             "{ " + 
758             "  identificationTag \"addAci\", " + 
759             "  precedence 14, " +
760             "  authenticationLevel none, " + 
761             "  itemOrUserFirst userFirst: " +
762             "  { " +
763             "    userClasses " + 
764             "    { " + 
765             "      allUsers " + 
766             "    }, " +
767             "    userPermissions " + 
768             "    { " + 
769             "      { " + 
770             "        protectedItems " + 
771             "        { " + 
772             "          entry, allUserAttributeTypesAndValues" + 
773             "        }, " +
774             "        grantsAndDenials " + 
775             "        { " + 
776             "          grantAdd, grantBrowse " +
777             "        } " +
778             "      } " +
779             "    } " + 
780             "  } " + 
781             "}"
782         );
783         
784         // prepare the subentry control to make the subentry visible
785         SubentriesControl control = new SubentriesControl();
786         control.setVisibility( true );
787         Control[] reqControls = new Control[] { control };
788         SearchControls searchControls = new SearchControls();
789         searchControls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
790         
791         ctx.setRequestControls( reqControls );
792         NamingEnumeration<SearchResult> enm = ctx.search( "", "(objectClass=*)", searchControls );
793         Set<String> results = new HashSet<String>();
794         
795         while ( enm.hasMore() )
796         {
797             SearchResult result = enm.next();
798             results.add( result.getName() );
799         }
800         
801         assertEquals( "expected results size of", 1, results.size() );
802         assertTrue( results.contains( "cn=anyBodyAdd" ) );
803     }
804 
805     
806     /**
807      * Create a person entry with multivalued RDN and check its content. This
808      * testcase was created to demonstrate DIRSERVER-628.
809      */
810     @Test
811     public void testMultiValuedRdnContent() throws Exception
812     {
813         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
814 
815         Attributes attrs = getPersonAttributes( "Bush", "Kate Bush" );
816         String rdn = "cn=Kate Bush+sn=Bush";
817         ctx.createSubcontext( rdn, attrs );
818 
819         SearchControls sctls = new SearchControls();
820         sctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
821         String filter = "(sn=Bush)";
822         String base = "";
823 
824         NamingEnumeration<SearchResult> enm = ctx.search( base, filter, sctls );
825         while ( enm.hasMore() )
826         {
827             SearchResult sr = enm.next();
828             attrs = sr.getAttributes();
829             Attribute cn = sr.getAttributes().get( "cn" );
830             assertNotNull( cn );
831             assertTrue( cn.contains( "Kate Bush" ) );
832             Attribute sn = sr.getAttributes().get( "sn" );
833             assertNotNull( sn );
834             assertTrue( sn.contains( "Bush" ) );
835         }
836 
837         ctx.destroySubcontext( rdn );
838     }
839 
840 
841     /**
842      * Create a person entry with multivalued RDN and check its name.
843      */
844     @Test
845     public void testMultiValuedRdnName() throws Exception
846     {
847         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
848 
849         Attributes attrs = getPersonAttributes( "Bush", "Kate Bush" );
850         String rdn = "cn=Kate Bush+sn=Bush";
851         DirContext entry = ctx.createSubcontext( rdn, attrs );
852         String nameInNamespace = entry.getNameInNamespace();
853 
854         SearchControls sctls = new SearchControls();
855         sctls.setSearchScope( SearchControls.OBJECT_SCOPE );
856         String filter = "(sn=Bush)";
857         String base = rdn;
858 
859         NamingEnumeration<SearchResult> enm = ctx.search( base, filter, sctls );
860         if ( enm.hasMore() )
861         {
862             SearchResult sr = enm.next();
863             assertNotNull( sr );
864             assertEquals( "Name in namespace", nameInNamespace, sr.getNameInNamespace() );
865         }
866         else
867         {
868             fail( "Entry not found:" + nameInNamespace );
869         }
870 
871         ctx.destroySubcontext( rdn );
872     }
873     
874     
875     @Test
876     public void testSearchJpeg() throws Exception
877     {
878         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
879 
880         SearchControls controls = new SearchControls();
881         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
882         NamingEnumeration<SearchResult> res = ctx.search( "", "(cn=Tori*)", controls );
883         
884         // collect all results 
885         while ( res.hasMore() )
886         {
887             SearchResult result = res.next();
888 
889             Attributes attrs = result.getAttributes();
890             
891             NamingEnumeration<? extends Attribute> all = attrs.getAll();
892                 
893             while ( all.hasMoreElements() )
894             {
895                 Attribute attr = all.next();
896                 
897                 if ( "jpegPhoto".equalsIgnoreCase( attr.getID() ) )
898                 {
899                     byte[] jpegVal = (byte[])attr.get();
900                     
901                     assertTrue( Arrays.equals( jpegVal, JPEG ) );
902                 }
903             }
904         }
905     }
906     
907     
908     @Test
909     public void testSearchOID() throws Exception
910     {
911         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
912 
913         SearchControls controls = new SearchControls();
914         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
915         NamingEnumeration<SearchResult> res = ctx.search( "", "(2.5.4.3=Tori*)", controls );
916         
917         // ensure that the entry "cn=Tori Amos" was found
918         assertTrue( res.hasMore() );
919 
920         SearchResult result = ( SearchResult ) res.next();
921 
922         // ensure that result is not null
923         assertNotNull( result );
924         
925         String rdn = result.getName();
926         
927         // ensure that the entry "cn=Tori Amos" was found
928         assertEquals( "cn=Tori Amos", rdn );
929         
930         // ensure that no other value was found
931         assertFalse( res.hasMore() );
932     }
933 
934     
935     @Test
936     public void testSearchAttrCN() throws Exception
937     {
938         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
939 
940         SearchControls controls = new SearchControls();
941         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
942         controls.setReturningAttributes( new String[]{"cn"} );
943         
944         NamingEnumeration<SearchResult> res = ctx.search( "", "(commonName=Tori*)", controls );
945         
946         assertTrue( res.hasMore() );
947         
948         SearchResult result = res.next();
949 
950         // ensure that result is not null
951         assertNotNull( result );
952         
953         Attributes attrs = result.getAttributes();
954         
955         // ensure the one and only attribute is "cn"
956         assertEquals( 1, attrs.size() );
957         assertNotNull( attrs.get( "cn" ) );
958         assertEquals( 1, attrs.get( "cn" ).size() );
959         assertEquals( "Tori Amos", ( String ) attrs.get("cn").get() );
960     }
961 
962     
963     @Test
964     public void testSearchAttrName() throws Exception
965     {
966         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
967 
968         SearchControls controls = new SearchControls();
969         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
970         controls.setReturningAttributes( new String[]{"name"} );
971         
972         NamingEnumeration<SearchResult> res = ctx.search( "", "(commonName=Tori*)", controls );
973         
974         assertTrue( res.hasMore() );
975         
976         SearchResult result = res.next();
977         
978         // ensure that result is not null
979         assertNotNull( result );
980         
981         Attributes attrs = result.getAttributes();
982         
983         // ensure that "cn" and "sn" are returned
984         assertEquals( 2, attrs.size() );
985         assertNotNull( attrs.get( "cn" ) );
986         assertEquals( 1, attrs.get("cn").size() );
987         assertEquals( "Tori Amos", ( String ) attrs.get( "cn" ).get() );
988         assertNotNull( attrs.get( "sn" ) );
989         assertEquals( 1, attrs.get( "sn" ).size() );
990         assertEquals( "Amos", ( String ) attrs.get( "sn" ).get() );
991     }
992 
993     
994     @Test
995     public void testSearchAttrCommonName() throws Exception
996     {
997         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
998 
999         SearchControls controls = new SearchControls();
1000         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1001         controls.setReturningAttributes( new String[] { "commonName" } );
1002         
1003         NamingEnumeration<SearchResult> res = ctx.search( "", "(commonName=Tori*)", controls );
1004         
1005         assertTrue( res.hasMore() );
1006         
1007 
1008         SearchResult result = res.next();
1009         
1010         // ensure that result is not null
1011         assertNotNull( result );
1012         
1013         Attributes attrs = result.getAttributes();
1014         
1015         // requested attribute was "commonName", but ADS returns "cn". 
1016         //       Other servers do the following:
1017         //       - OpenLDAP: also return "cn"
1018         //       - Siemens DirX: return "commonName"
1019         //       - Sun Directory 5.2: return "commonName"
1020         // ensure the one and only attribute is "cn"
1021         assertEquals( 1, attrs.size() );
1022         assertNotNull( attrs.get("cn") );
1023         assertEquals( 1, attrs.get("cn").size() );
1024         assertEquals( "Tori Amos", (String)attrs.get("cn").get() );
1025     }
1026     
1027 
1028     @Test
1029     public void testSearchAttrOID() throws Exception
1030     {
1031         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1032 
1033         SearchControls controls = new SearchControls();
1034         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1035         controls.setReturningAttributes( new String[]{"2.5.4.3"} );
1036         
1037         NamingEnumeration<SearchResult> res = ctx.search( "", "(commonName=Tori*)", controls );
1038         
1039         assertTrue( res.hasMore() );
1040         
1041         SearchResult result = res.next();
1042         
1043         // ensure that result is not null
1044         assertNotNull( result );
1045         
1046         Attributes attrs = result.getAttributes();
1047         
1048         // requested attribute was "2.5.4.3", but ADS returns "cn". 
1049         //       Other servers do the following:
1050         //       - OpenLDAP: also return "cn"
1051         //       - Siemens DirX: also return "cn"
1052         //       - Sun Directory 5.2: return "2.5.4.3"
1053         // ensure the one and only attribute is "cn"
1054         assertEquals( 1, attrs.size() );
1055         assertNotNull( attrs.get("cn") );
1056         assertEquals( 1, attrs.get("cn").size() );
1057         assertEquals( "Tori Amos", (String)attrs.get("cn").get() );
1058     }
1059     
1060     
1061     @Test
1062     public void testSearchAttrC_L() throws Exception
1063     {
1064         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1065 
1066         // create administrative area
1067         Attributes aaAttrs = new BasicAttributes( true );
1068         Attribute aaObjectClass = new BasicAttribute( "objectClass" );
1069         aaObjectClass.add( "top" );
1070         aaObjectClass.add( "organizationalUnit" );
1071         aaObjectClass.add( "extensibleObject" );
1072         aaAttrs.put( aaObjectClass );
1073         aaAttrs.put( "ou", "Collective Area" );
1074         aaAttrs.put( "administrativeRole", "collectiveAttributeSpecificArea" );
1075         DirContext aaCtx = ctx.createSubcontext( "ou=Collective Area", aaAttrs );
1076         
1077         // create subentry
1078         Attributes subentry = new BasicAttributes( true );
1079         Attribute objectClass = new BasicAttribute( "objectClass" );
1080         objectClass.add( "top" );
1081         objectClass.add( SchemaConstants.SUBENTRY_OC );
1082         objectClass.add( "collectiveAttributeSubentry" );
1083         subentry.put( objectClass );
1084         subentry.put( "c-l", "Munich" );
1085         subentry.put( "cn", "Collective Subentry" );
1086         subentry.put( "subtreeSpecification", "{ }" );
1087         aaCtx.createSubcontext( "cn=Collective Subentry", subentry );
1088         
1089         // create real enty
1090         Attributes attributes = this.getPersonAttributes( "Bush", "Kate Bush" );
1091         aaCtx.createSubcontext( "cn=Kate Bush", attributes );
1092         
1093         // search
1094         SearchControls controls = new SearchControls();
1095         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1096         controls.setReturningAttributes( new String[]{"c-l" } );
1097         
1098         NamingEnumeration<SearchResult> res = aaCtx.search( "", "(cn=Kate Bush)", controls );
1099         
1100         assertTrue( res.hasMore() );
1101         
1102         SearchResult result = res.next();
1103         
1104         // ensure that result is not null
1105         assertNotNull( result );
1106         
1107         Attributes attrs = result.getAttributes();
1108         
1109         // ensure the one and only attribute is "c-l"
1110         assertEquals( 1, attrs.size() );
1111         assertNotNull( attrs.get("c-l") );
1112         assertEquals( 1, attrs.get("c-l").size() );
1113         assertEquals( "Munich", (String)attrs.get("c-l").get() );
1114     }
1115 
1116 
1117     @Test
1118     public void testSearchUsersAttrs() throws Exception
1119     {
1120         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1121 
1122         SearchControls controls = new SearchControls();
1123         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1124         controls.setReturningAttributes( new String[]{"*"} );
1125         
1126         NamingEnumeration<SearchResult> res = ctx.search( "", "(commonName=Tori Amos)", controls );
1127         
1128         assertTrue( res.hasMore() );
1129         
1130         SearchResult result = res.next();
1131         
1132         // ensure that result is not null
1133         assertNotNull( result );
1134         
1135         Attributes attrs = result.getAttributes();
1136         
1137         // ensure that all user attributes are returned
1138         assertEquals( 6, attrs.size() );
1139         assertNotNull( attrs.get( "cn" ) );
1140         assertNotNull( attrs.get( "sn" ) );
1141         assertNotNull( attrs.get( "objectClass" ) );
1142         assertNotNull( attrs.get( "jpegPhoto" ) );
1143         assertNotNull( attrs.get( "description" ) );
1144         assertNotNull( attrs.get( "userCertificate" ) );
1145         assertNull( attrs.get( "createtimestamp" ) );
1146         assertNull( attrs.get( "creatorsname" ) );
1147     }
1148 
1149 
1150     @Test
1151     public void testSearchOperationalAttrs() throws Exception
1152     {
1153         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1154 
1155         SearchControls controls = new SearchControls();
1156         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1157         controls.setReturningAttributes( new String[]{"+"} );
1158         
1159         NamingEnumeration<SearchResult> res = ctx.search( "", "(commonName=Tori Amos)", controls );
1160         
1161         assertTrue( res.hasMore() );
1162         
1163         SearchResult result = res.next();
1164         
1165         // ensure that result is not null
1166         assertNotNull( result );
1167         
1168         Attributes attrs = result.getAttributes();
1169         
1170         // ensure that all operational attributes are returned
1171         // and no user attributes
1172         assertEquals( 2, attrs.size() );
1173         assertNull( attrs.get( "cn" ) );
1174         assertNull( attrs.get( "sn" ) );
1175         assertNull( attrs.get( "objectClass" ) );
1176         assertNull( attrs.get( "jpegPhoto" ) );
1177         assertNull( attrs.get( "description" ) );
1178         assertNotNull( attrs.get( "createtimestamp" ) );
1179         assertNotNull( attrs.get( "creatorsname" ) );
1180     }
1181     
1182 
1183     @Test
1184     public void testSearchAllAttrs() throws Exception
1185     {
1186         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1187 
1188         SearchControls controls = new SearchControls();
1189         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1190         controls.setReturningAttributes( new String[]{"+", "*"} );
1191         
1192         NamingEnumeration<SearchResult> res = ctx.search( "", "(commonName=Tori Amos)", controls );
1193         
1194         assertTrue( res.hasMore() );
1195         
1196         SearchResult result = ( SearchResult ) res.next();
1197         
1198         // ensure that result is not null
1199         assertNotNull( result );
1200         
1201         Attributes attrs = result.getAttributes();
1202         
1203         // ensure that all user attributes are returned
1204         assertEquals( 8, attrs.size() );
1205         assertNotNull( attrs.get( "cn" ) );
1206         assertNotNull( attrs.get( "sn" ) );
1207         assertNotNull( attrs.get( "objectClass" ) );
1208         assertNotNull( attrs.get( "jpegPhoto" ) );
1209         assertNotNull( attrs.get( "userCertificate" ) );
1210         assertNotNull( attrs.get( "description" ) );
1211         assertNotNull( attrs.get( "createtimestamp" ) );
1212         assertNotNull( attrs.get( "creatorsname" ) );
1213     }
1214 
1215 
1216     @Test
1217     public void testSearchBadDN() throws Exception
1218     {
1219         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1220         SearchControls controls = new SearchControls();
1221         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1222         
1223         try
1224         {
1225             ctx.search( "cn=admin", "(objectClass=*)", controls );
1226         }
1227         catch ( NameNotFoundException nnfe )
1228         {
1229             assertTrue( true );
1230         }
1231     }
1232     
1233 
1234     @Test
1235     public void testSearchInvalidDN() throws Exception
1236     {
1237         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1238 
1239         SearchControls controls = new SearchControls();
1240         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
1241         
1242         try
1243         {
1244             ctx.search( "myBadDN", "(objectClass=*)", controls );
1245             fail();
1246         }
1247         catch ( NamingException ne )
1248         {
1249             assertTrue( true );
1250         }
1251     }
1252     
1253 
1254     /**
1255      * Check if operational attributes are present, if "+" is requested.
1256      */
1257     @Test
1258     public void testSearchOperationalAttributes() throws Exception
1259     {
1260         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1261         SearchControls ctls = new SearchControls();
1262 
1263         ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
1264         ctls.setReturningAttributes( new String[]
1265             { "+" } );
1266 
1267         NamingEnumeration<SearchResult> result = ctx.search( HEATHER_RDN, FILTER, ctls );
1268 
1269         if ( result.hasMore() )
1270         {
1271             SearchResult entry = result.next();
1272 
1273             String[] opAttrNames =
1274                 { "creatorsName", "createTimestamp" };
1275 
1276             checkForAttributes( entry.getAttributes(), opAttrNames );
1277         }
1278         else
1279         {
1280             fail( "entry " + HEATHER_RDN + " not found" );
1281         }
1282 
1283         result.close();
1284     }
1285 
1286 
1287     /**
1288      * Check if user attributes are present, if "*" is requested.
1289      */
1290     @Test
1291     public void testSearchUserAttributes() throws Exception
1292     {
1293         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1294         SearchControls ctls = new SearchControls();
1295 
1296         ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
1297         ctls.setReturningAttributes( new String[]
1298             { "*" } );
1299 
1300         NamingEnumeration<SearchResult> result = ctx.search( HEATHER_RDN, FILTER, ctls );
1301 
1302         if ( result.hasMore() )
1303         {
1304             SearchResult entry = result.next();
1305 
1306             String[] userAttrNames =
1307                 { "objectClass", "sn", "cn" };
1308 
1309             checkForAttributes( entry.getAttributes(), userAttrNames );
1310         }
1311         else
1312         {
1313             fail( "entry " + HEATHER_RDN + " not found" );
1314         }
1315 
1316         result.close();
1317     }
1318     
1319     
1320     /**
1321      * Check if user and operational attributes are present, if both "*" and "+" are requested.
1322      */
1323     @Test
1324     public void testSearchOperationalAndUserAttributes() throws Exception
1325     {
1326         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1327         SearchControls ctls = new SearchControls();
1328  
1329         ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
1330         ctls.setReturningAttributes( new String[]
1331             { "+", "*" } );
1332 
1333         String[] userAttrNames =
1334             { "objectClass", "sn", "cn" };
1335 
1336         String[] opAttrNames =
1337             { "creatorsName", "createTimestamp" };
1338 
1339         NamingEnumeration<SearchResult> result = ctx.search( HEATHER_RDN, FILTER, ctls );
1340 
1341         if ( result.hasMore() )
1342         {
1343             SearchResult entry = result.next();
1344             Attributes attrs = entry.getAttributes();
1345 
1346             assertNotNull( attrs );
1347 
1348             checkForAttributes( attrs, userAttrNames );
1349             checkForAttributes( attrs, opAttrNames );
1350         }
1351         else
1352         {
1353             fail( "entry " + HEATHER_RDN + " not found" );
1354         }
1355 
1356         result.close();
1357 
1358         ctls.setReturningAttributes( new String[]
1359             { "*", "+" } );
1360 
1361         result = ctx.search( HEATHER_RDN, FILTER, ctls );
1362 
1363         if ( result.hasMore() )
1364         {
1365             SearchResult entry = ( SearchResult ) result.next();
1366             Attributes attrs = entry.getAttributes();
1367 
1368             assertNotNull( attrs );
1369             
1370             checkForAttributes( attrs, userAttrNames );
1371             checkForAttributes( attrs, opAttrNames );
1372         }
1373         else
1374         {
1375             fail( "entry " + HEATHER_RDN + " not found" );
1376         }
1377 
1378         result.close();
1379     }
1380    
1381     
1382     /**
1383      * Test for DIRSERVER-1180 where search hangs when an invalid a substring 
1384      * expression missing an any field is used in a filter: i.e. (cn=**).
1385      * 
1386      * @see https://issues.apache.org/jira/browse/DIRSERVER-1180
1387      */
1388     @Test
1389     public void testMissingAnyInSubstring_DIRSERVER_1180() throws Exception
1390     {
1391         LdapContext ctx = ( LdapContext ) getWiredContext( ldapService ).lookup( BASE );
1392         Attributes attrs = new BasicAttributes( "objectClass", "inetOrgPerson", true );
1393         attrs.get( "objectClass" ).add( "organizationalPerson" );
1394         attrs.get( "objectClass" ).add( "person" );
1395         attrs.put( "givenName", "Jim" );
1396         attrs.put( "sn", "Bean" );
1397         attrs.put( "cn", "jimbean" );
1398         
1399         ctx.createSubcontext( "cn=jimbean", attrs );
1400         
1401         try
1402         {
1403             ctx.search( "", "(cn=**)", new SearchControls() );
1404             fail();
1405         }
1406         catch ( Exception e )
1407         {
1408             assertTrue( true );
1409         }
1410     }
1411 }