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 org.apache.directory.server.core.entry.ClonedServerEntry;
24  import org.apache.directory.server.core.filtering.EntryFilter;
25  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
26  import org.apache.directory.server.core.integ.Level;
27  import org.apache.directory.server.core.integ.annotations.ApplyLdifs;
28  import org.apache.directory.server.core.integ.annotations.CleanupLevel;
29  import org.apache.directory.server.core.interceptor.BaseInterceptor;
30  import org.apache.directory.server.core.interceptor.Interceptor;
31  import org.apache.directory.server.core.interceptor.NextInterceptor;
32  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
33  import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
34  import org.apache.directory.server.integ.SiRunner;
35  import static org.apache.directory.server.integ.ServerIntegrationUtils.getWiredContext;
36  import static org.junit.Assert.*;
37  
38  import org.apache.directory.server.ldap.LdapService;
39  import org.junit.After;
40  import org.junit.Before;
41  import org.junit.Test;
42  import org.junit.runner.RunWith;
43  
44  import javax.naming.NamingEnumeration;
45  import javax.naming.SizeLimitExceededException;
46  import javax.naming.TimeLimitExceededException;
47  import javax.naming.directory.DirContext;
48  import javax.naming.directory.SearchControls;
49  import javax.naming.directory.SearchResult;
50  
51  import java.util.HashSet;
52  import java.util.Set;
53  
54  
55  /**
56   * A set of tests to make sure the negation operator is working 
57   * properly when included in search filters on indexed attributes.
58   * 
59   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
60   * @version $Rev$, $Date$
61   */
62  @RunWith ( SiRunner.class ) 
63  @CleanupLevel ( Level.SUITE )
64  @ApplyLdifs( {
65      "dn: ou=actors,ou=system\n" +
66      "objectClass: top\n" +
67      "objectClass: organizationalUnit\n" +
68      "ou: actors\n\n" +
69  
70      "dn: uid=jblack,ou=actors,ou=system\n" +
71      "objectClass: top\n" +
72      "objectClass: person\n" +
73      "objectClass: organizationalPerson\n" +
74      "objectClass: uidObject\n" +
75      "uid: jblack\n" +
76      "ou: comedy\n" +
77      "ou: adventure\n" +
78      "cn: Jack Black\n" +
79      "userPassword: secret\n" +
80      "sn: Black\n\n" +
81  
82      "dn: uid=bpitt,ou=actors,ou=system\n" +
83      "objectClass: top\n" +
84      "objectClass: person\n" +
85      "objectClass: organizationalPerson\n" +
86      "objectClass: uidObject\n" +
87      "uid: bpitt\n" +
88      "ou: drama\n" +
89      "ou: adventure\n" +
90      "userPassword: secret\n" +
91      "cn: Brad Pitt\n" +
92      "sn: Pitt\n\n" +
93  
94      "dn: uid=gcloony,ou=actors,ou=system\n" +
95      "objectClass: top\n" +
96      "objectClass: person\n" +
97      "objectClass: organizationalPerson\n" +
98      "objectClass: uidObject\n" +
99      "uid: gcloony\n" +
100     "ou: drama\n" +
101     "userPassword: secret\n" +
102     "cn: Goerge Cloony\n" +
103     "sn: Cloony\n\n" +
104 
105     "dn: uid=jnewbie,ou=actors,ou=system\n" +
106     "objectClass: top\n" +
107     "objectClass: person\n" +
108     "objectClass: organizationalPerson\n" +
109     "objectClass: uidObject\n" +
110     "uid: jnewbie\n" +
111     "userPassword: secret\n" +
112     "cn: Joe Newbie\n" +
113     "sn: Newbie\n\n" 
114     }
115 )
116 public class SearchLimitsIT 
117 {
118     public static LdapService ldapService;
119 
120     
121     /**
122      * An {@link Interceptor} that fakes a specified amount of delay to each 
123      * search iteration so we can make sure search time limits are adhered to.
124      *
125      * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
126      * @version $Rev$, $Date$
127      */
128     class DelayInducingInterceptor extends BaseInterceptor
129     {
130         private Long delayMillis;
131 
132         
133         public EntryFilteringCursor search( NextInterceptor next, SearchOperationContext opContext ) throws Exception
134         {
135             EntryFilteringCursor cursor = next.search( opContext );
136             cursor.addEntryFilter( new EntryFilter() {
137                 public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception
138                 {
139                     if ( delayMillis != null )
140                     {
141                         Thread.sleep( delayMillis );
142                     }
143 
144                     return true;
145                 }
146             });
147             return cursor;
148         }
149         
150         
151         public void setDelayMillis( long delayMillis )
152         {
153             if ( delayMillis <= 0 )
154             {
155                 this.delayMillis = null;
156             }
157             
158             this.delayMillis = delayMillis;
159         }
160     }
161 
162     
163     private int oldMaxTimeLimit;
164     private int oldMaxSizeLimit;
165     private DelayInducingInterceptor delayInterceptor;
166 
167     
168     @Before
169     public void setUp() throws Exception
170     {
171         oldMaxTimeLimit = ldapService.getMaxTimeLimit();
172         oldMaxSizeLimit = ldapService.getMaxSizeLimit();
173         delayInterceptor = new DelayInducingInterceptor();
174         ldapService.getDirectoryService().getInterceptorChain().addFirst( delayInterceptor );
175     }
176     
177     
178     @After
179     public void tearDown() throws Exception
180     {
181         ldapService.setMaxTimeLimit( oldMaxTimeLimit );
182         ldapService.setMaxSizeLimit( oldMaxSizeLimit );
183         ldapService.getDirectoryService().getInterceptorChain().remove( DelayInducingInterceptor.class.getName() );
184     }
185     
186 
187     // -----------------------------------------------------------------------
188     // Time Limit Tests
189     // -----------------------------------------------------------------------
190     
191     
192     /**
193      * Sets up the server with unlimited search time limit but constrains time
194      * by request time limit value to cause a time limit exceeded exception on
195      * the client.
196      */
197     @Test ( expected = TimeLimitExceededException.class )
198     public void testRequestConstrainedUnlimitByConfiguration() throws Exception
199     {
200         ldapService.setMaxTimeLimit( LdapService.NO_TIME_LIMIT );
201         delayInterceptor.setDelayMillis( 500 );
202         
203         getActorsWithLimit( "(objectClass=*)", 499, 0 );
204     }
205     
206 
207     /**
208      * Sets up the server with longer search time limit than the request's 
209      * which constrains time by request time limit value to cause a time limit 
210      * exceeded exception on the client.
211      */
212     @Test ( expected = TimeLimitExceededException.class )
213     public void testRequestConstrainedLessThanConfiguration() throws Exception
214     {
215         ldapService.setMaxTimeLimit( 10000 ); // this is in seconds
216         delayInterceptor.setDelayMillis( 500 );
217         
218         getActorsWithLimit( "(objectClass=*)", 499, 0 );
219     }
220 
221     
222     /**
223      * Sets up the server with shorter search time limit than the request's 
224      * which constrains time by using server max limit value to cause a time 
225      * limit exceeded exception on the client.
226      */
227     @Test ( expected = TimeLimitExceededException.class )
228     public void testRequestConstrainedGreaterThanConfiguration() throws Exception
229     {
230         ldapService.setMaxTimeLimit( 1 ); // this is in seconds
231         delayInterceptor.setDelayMillis( 1100 );
232         
233         getActorsWithLimit( "(objectClass=*)", 100000, 0 );
234     }
235 
236     
237     /**
238      * Sets up the server with limited search time with unlimited request
239      * time limit.  Should work just fine for the administrative user.
240      */
241     @Test 
242     public void testRequestUnlimitedConfigurationLimited() throws Exception
243     {
244         ldapService.setMaxTimeLimit( 1 ); // this is in seconds
245         delayInterceptor.setDelayMillis( 500 );
246         
247         getActorsWithLimit( "(objectClass=*)", 0, 0 );
248     }
249 
250     
251     /**
252      * Sets up the server with limited search time with unlimited request
253      * time limit.  Should not work for non administrative users.
254      */
255     @Test ( expected = TimeLimitExceededException.class ) 
256     public void testNonAdminRequestUnlimitedConfigurationLimited() throws Exception
257     {
258         ldapService.setMaxTimeLimit( 1 ); // this is in seconds
259         delayInterceptor.setDelayMillis( 500 );
260         
261         getActorsWithLimitNonAdmin( "(objectClass=*)", 0, 0 );
262     }
263     
264     
265     // -----------------------------------------------------------------------
266     // Size Limit Tests
267     // -----------------------------------------------------------------------
268     
269     
270     /**
271      * Sets up the server with unlimited search size limit but constrains size
272      * by request size limit value to cause a size limit exceeded exception on
273      * the client.
274      */
275     @Test ( expected = SizeLimitExceededException.class )
276     public void testRequestConstrainedUnlimitByConfigurationSize() throws Exception
277     {
278         ldapService.setMaxSizeLimit( LdapService.NO_SIZE_LIMIT );
279         getActorsWithLimit( "(objectClass=*)", 0,  1 );
280     }
281     
282 
283     /**
284      * Sets up the server with longer search size limit than the request's 
285      * which constrains size by request size limit value to cause a size limit 
286      * exceeded exception on the client.
287      */
288     @Test ( expected = SizeLimitExceededException.class )
289     public void testRequestConstrainedLessThanConfigurationSize() throws Exception
290     {
291         ldapService.setMaxSizeLimit( 10000 ); 
292         getActorsWithLimit( "(objectClass=*)", 0, 1 );
293     }
294 
295 
296     /**
297      * Sets up the server with shorter search size limit than the request's 
298      * which constrains size by using server max limit value. Should work 
299      * just fine for the administrative user.
300      */
301     @Test
302     public void testRequestConstrainedGreaterThanConfigurationSize() throws Exception
303     {
304         ldapService.setMaxSizeLimit( 1 ); 
305         Set<String> set = getActorsWithLimit( "(objectClass=*)", 0, 100000 );
306         assertEquals( 4, set.size() );
307     }
308 
309 
310     /**
311      * Sets up the server with shorter search size limit than the request's 
312      * which constrains size by using server max limit value to cause a size 
313      * limit exceeded exception on the client.
314      */
315     @Test ( expected = SizeLimitExceededException.class )
316     public void testNonAdminRequestConstrainedGreaterThanConfigurationSize() throws Exception
317     {
318         ldapService.setMaxSizeLimit( 1 ); 
319         getActorsWithLimitNonAdmin( "(objectClass=*)", 0, 100000 );
320     }
321 
322     
323     /**
324      * Sets up the server with limited search size with unlimited request
325      * size limit.  Should work just fine for the administrative user.
326      */
327     @Test 
328     public void testRequestUnlimitedConfigurationLimitedSize() throws Exception
329     {
330         ldapService.setMaxSizeLimit( 1 ); 
331         Set<String> set = getActorsWithLimit( "(objectClass=*)", 0, 0 );
332         assertEquals( 4, set.size() );
333     }
334 
335     
336     /**
337      * Sets up the server with limited search size with unlimited request
338      * size limit.  Should not work for non administrative users.
339      */
340     @Test ( expected = SizeLimitExceededException.class ) 
341     public void testNonAdminRequestUnlimitedConfigurationLimitedSize() throws Exception
342     {
343         ldapService.setMaxSizeLimit( 1 ); // this is in seconds
344         getActorsWithLimitNonAdmin( "(objectClass=*)", 0, 0 );
345     }
346 
347 
348     /**
349      * Test for DIRSERVER-1235.
350      * Sets up the server with unlimited search size limit but constrains size
351      * by request size limit value. The request size limit is less than the
352      * expected number of result entries, so exception expected.
353      */
354     @Test(expected = SizeLimitExceededException.class)
355     public void testRequestConstraintedLessThanExpectedSize() throws Exception
356     {
357         ldapService.setMaxSizeLimit( LdapService.NO_SIZE_LIMIT );
358         getActorsWithLimit( "(objectClass=*)", 0, 3 );
359     }
360 
361 
362     /**
363      * Test for DIRSERVER-1235.
364      * Sets up the server with unlimited search size limit but constrains size
365      * by request size limit value. The request size limit is equal to the
366      * expected number of result entries so no exception expected.
367      */
368     @Test
369     public void testRequestConstraintedEqualToExpectedSize() throws Exception
370     {
371         ldapService.setMaxSizeLimit( LdapService.NO_SIZE_LIMIT );
372         Set<String> set = getActorsWithLimit( "(objectClass=*)", 0, 4 );
373         assertEquals( 4, set.size() );
374     }
375 
376 
377     /**
378      * Test for DIRSERVER-1235.
379      * Sets up the server with unlimited search size limit but constrains size
380      * by request size limit value. The request size limit is greater than the
381      * expected number of result entries so no exception expected.
382      */
383     @Test
384     public void testRequestConstraintedGreaterThanExpectedSize() throws Exception
385     {
386         ldapService.setMaxSizeLimit( LdapService.NO_SIZE_LIMIT );
387         Set<String> set = getActorsWithLimit( "(objectClass=*)", 0, 5 );
388         assertEquals( 4, set.size() );
389     }
390 
391 
392     /**
393      * Test for DIRSERVER-1235.
394      * Reads an entry using object scope and size limit 1, no exception
395      * expected.
396      */
397     @Test
398     public void testRequestObjectScopeAndSizeLimit() throws Exception
399     {
400         ldapService.setMaxSizeLimit( LdapService.NO_SIZE_LIMIT );
401 
402         DirContext ctx = getWiredContext( ldapService );
403         String filter = "(objectClass=*)";
404         SearchControls controls = new SearchControls();
405         controls.setTimeLimit( 0 );
406         controls.setCountLimit( 1 );
407         controls.setSearchScope( SearchControls.OBJECT_SCOPE );
408 
409         NamingEnumeration<SearchResult> namingEnumeration = ctx.search( "ou=actors,ou=system", filter, controls );
410         assertTrue( namingEnumeration.hasMore() );
411         namingEnumeration.next();
412         assertFalse( namingEnumeration.hasMore() );
413     }
414 
415 
416     // -----------------------------------------------------------------------
417     // Utility Methods
418     // -----------------------------------------------------------------------
419     
420     
421     private Set<String> getActorsWithLimit( String filter, int timeLimitMillis, int sizeLimit ) throws Exception
422     {
423         DirContext ctx = getWiredContext( ldapService );
424         Set<String> results = new HashSet<String>();
425         SearchControls controls = new SearchControls();
426         controls.setTimeLimit( timeLimitMillis );
427         controls.setCountLimit( sizeLimit );
428         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
429         
430         NamingEnumeration<SearchResult> namingEnumeration = ctx.search( "ou=actors,ou=system", filter, controls );
431         while( namingEnumeration.hasMore() )
432         {
433             results.add( namingEnumeration.next().getNameInNamespace() );
434         }
435         
436         return results;
437     }
438 
439     
440     private Set<String> getActorsWithLimitNonAdmin( String filter, int timeLimitMillis, int sizeLimit ) 
441         throws Exception
442     {
443         DirContext ctx = getWiredContext( ldapService, "uid=jblack,ou=actors,ou=system", "secret" );
444         Set<String> results = new HashSet<String>();
445         SearchControls controls = new SearchControls();
446         controls.setTimeLimit( timeLimitMillis );
447         controls.setCountLimit( sizeLimit );
448         controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
449         
450         NamingEnumeration<SearchResult> namingEnumeration = ctx.search( "ou=actors,ou=system", filter, controls );
451         while( namingEnumeration.hasMore() )
452         {
453             results.add( namingEnumeration.next().getNameInNamespace() );
454         }
455         
456         return results;
457     }
458 }