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.modify;
21  
22  
23  import javax.naming.NamingEnumeration;
24  import javax.naming.directory.Attribute;
25  import javax.naming.directory.Attributes;
26  import javax.naming.directory.BasicAttribute;
27  import javax.naming.directory.BasicAttributes;
28  import javax.naming.directory.DirContext;
29  import javax.naming.directory.InvalidAttributeIdentifierException;
30  import javax.naming.directory.InvalidAttributeValueException;
31  import javax.naming.directory.ModificationItem;
32  import javax.naming.directory.NoSuchAttributeException;
33  import javax.naming.directory.SchemaViolationException;
34  import javax.naming.directory.SearchControls;
35  import javax.naming.directory.SearchResult;
36  
37  import org.apache.directory.server.core.integ.Level;
38  import org.apache.directory.server.core.integ.annotations.ApplyLdifs;
39  import org.apache.directory.server.core.integ.annotations.CleanupLevel;
40  import org.apache.directory.server.integ.SiRunner;
41  import static org.apache.directory.server.integ.ServerIntegrationUtils.getWiredContext;
42  
43  import org.apache.directory.server.ldap.LdapService;
44  import org.junit.Test;
45  import org.junit.runner.RunWith;
46  import static org.junit.Assert.fail;
47  import static org.junit.Assert.assertNull;
48  import static org.junit.Assert.assertTrue;
49  import static org.junit.Assert.assertEquals;
50  import static org.junit.Assert.assertNotNull;
51  
52  
53  /**
54   * Test case with different modify operations on a person entry. Each includes a
55   * single removal operation only.
56   * 
57   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
58   * @version $Rev: 692919 $
59   */
60  @RunWith ( SiRunner.class ) 
61  @CleanupLevel ( Level.SUITE )
62  @ApplyLdifs( {
63      // Entry # 1
64      "dn: cn=Tori Amos,ou=system\n" +
65      "objectClass: person\n" +
66      "objectClass: top\n" +
67      "description: an American singer-songwriter\n" +
68      "cn: Tori Amos\n" +
69      "sn: Amos\n\n"
70      }
71  )
72  public class ModifyRemoveIT
73  {
74      private static final String BASE = "ou=system";
75      private static final String RDN = "cn=Tori Amos";
76  
77      
78      public static LdapService ldapService;
79      
80  
81      /**
82       * Creation of required attributes of a person entry.
83       */
84      protected Attributes getPersonAttributes( String sn, String cn )
85      {
86          Attributes attributes = new BasicAttributes( true );
87          Attribute attribute = new BasicAttribute( "objectClass" );
88          attribute.add( "top" );
89          attribute.add( "person" );
90          attributes.put( attribute );
91          attributes.put( "cn", cn );
92          attributes.put( "sn", sn );
93  
94          return attributes;
95      }
96  
97  
98      /**
99       * Creation of required attributes of an inetOrgPerson entry.
100      */
101     protected Attributes getInetOrgPersonAttributes( String sn, String cn )
102     {
103         Attributes attrs = new BasicAttributes( true );
104         Attribute ocls = new BasicAttribute( "objectClass" );
105         ocls.add( "top" );
106         ocls.add( "person" );
107         ocls.add( "organizationalPerson" );
108         ocls.add( "inetOrgPerson" );
109         attrs.put( ocls );
110         attrs.put( "cn", cn );
111         attrs.put( "sn", sn );
112 
113         return attrs;
114     }
115 
116 
117     /**
118      * Remove an attribute which does not exist in an attribute making sure 
119      * it does not remove other values in that attribute.  Tests if the 
120      * following JIRA issue is still valid:
121      * 
122      *    https://issues.apache.org/jira/browse/DIRSERVER-1149
123      */
124     @Test
125     public void testRemoveAttemptWithoutChange() throws Exception
126     {
127         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
128         
129         // Get the attributes and check the contents
130         Attributes tori = ctx.getAttributes( RDN );
131         assertNotNull( tori.get( "objectClass" ) );
132         assertNotNull( tori.get( "cn" ) );
133         assertEquals( 1, tori.get( "cn" ).size() );
134         assertEquals( "Tori Amos", tori.get( "cn" ).get() );
135         assertNotNull( tori.get( "sn" ) );
136         
137         // Test an add operation first
138         ModificationItem mod = new ModificationItem( DirContext.ADD_ATTRIBUTE, new BasicAttribute( "cn", "foo" ) );
139         ctx.modifyAttributes( RDN, new ModificationItem[] { mod } );
140         tori = ctx.getAttributes( RDN );
141         assertNotNull( tori.get( "objectClass" ) );
142         assertNotNull( tori.get( "cn" ) );
143         assertEquals( 2, tori.get( "cn" ).size() );
144         assertEquals( "Tori Amos", tori.get( "cn" ).get( 0 ) );
145         assertEquals( "foo", tori.get( "cn" ).get( 1 ) );
146         assertNotNull( tori.get( "sn" ) );
147         
148         // Now test remove of value ( bar ) that does not exist in cn
149         mod = new ModificationItem( DirContext.REMOVE_ATTRIBUTE, new BasicAttribute( "cn", "bar" ) );
150         ctx.modifyAttributes( RDN, new ModificationItem[] { mod } );
151         tori = ctx.getAttributes( RDN );
152         assertNotNull( tori.get( "objectClass" ) );
153         assertNotNull( tori.get( "cn" ) );
154         assertEquals( 2, tori.get( "cn" ).size() );
155         assertEquals( "Tori Amos", tori.get( "cn" ).get( 0 ) );
156         assertEquals( "foo", tori.get( "cn" ).get( 1 ) );
157         assertNotNull( tori.get( "sn" ) );
158     }
159 
160 
161     /**
162      * Remove an attribute, which is not required.
163      * 
164      * Expected result: After successful deletion, attribute is not present in
165      * entry.
166      */
167     @Test
168     public void testRemoveNotRequiredAttribute() throws Exception
169     {
170         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
171         
172         // Remove description Attribute
173         Attribute attr = new BasicAttribute( "description" );
174         Attributes attrs = new BasicAttributes( true );
175         attrs.put( attr );
176         ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, attrs );
177 
178         // Verify, that attribute is deleted
179         attrs = ctx.getAttributes( RDN );
180         attr = attrs.get( "description" );
181         assertNull( attr );
182     }
183 
184 
185     /**
186      * Remove two not required attributes.
187      * 
188      * Expected result: After successful deletion, both attributes ar not
189      * present in entry.
190      */
191     @Test
192     public void testRemoveTwoNotRequiredAttributes() throws Exception
193     {
194         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
195         
196         // add telephoneNumber to entry
197         Attributes tn = new BasicAttributes( "telephoneNumber", "12345678", true );
198         ctx.modifyAttributes( RDN, DirContext.ADD_ATTRIBUTE, tn );
199 
200         // Remove description and telephoneNumber to Attribute
201         Attributes attrs = new BasicAttributes( true );
202         attrs.put( new BasicAttribute( "description" ) );
203         attrs.put( new BasicAttribute( "telephoneNumber" ) );
204         ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, attrs );
205 
206         // Verify, that attributes are deleted
207         attrs = ctx.getAttributes( RDN );
208         assertNull( attrs.get( "description" ) );
209         assertNull( attrs.get( "telephoneNumber" ) );
210         assertNotNull( attrs.get( "cn" ) );
211         assertNotNull( attrs.get( "sn" ) );
212     }
213 
214 
215     /**
216      * Remove a required attribute. The sn attribute of the person entry is used
217      * here.
218      * 
219      * Expected Result: Deletion fails with NamingException (Schema Violation).
220      */
221     @Test
222     public void testRemoveRequiredAttribute() throws Exception
223     {
224         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
225         
226         // Remove sn attribute
227         Attribute attr = new BasicAttribute( "sn" );
228         Attributes attrs = new BasicAttributes( true );
229         attrs.put( attr );
230 
231         try
232         {
233             ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, attrs );
234             fail( "Deletion of required attribute should fail." );
235         }
236         catch ( SchemaViolationException e )
237         {
238             // expected behaviour
239         }
240     }
241 
242 
243     /**
244      * Remove a required attribute from RDN.
245      * 
246      * Expected Result: Deletion fails with SchemaViolationException.
247      */
248     @Test
249     public void testRemovePartOfRdn() throws Exception
250     {
251         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
252         
253         // Remove sn attribute
254         Attribute attr = new BasicAttribute( "cn" );
255         Attributes attrs = new BasicAttributes( true );
256         attrs.put( attr );
257 
258         try
259         {
260             ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, attrs );
261             fail( "Deletion of RDN attribute should fail." );
262         }
263         catch ( SchemaViolationException e )
264         {
265             // expected behaviour
266         }
267     }
268 
269 
270     /**
271      * Remove a not required attribute from RDN.
272      * 
273      * Expected Result: Deletion fails with SchemaViolationException.
274      */
275     @Test
276     public void testRemovePartOfRdnNotRequired() throws Exception
277     {
278         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
279         
280         // Change RDN to another attribute
281         String newRdn = "description=an American singer-songwriter";
282         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
283         ctx.rename( RDN, newRdn );
284 
285         // Remove description, which is now RDN attribute
286         Attribute attr = new BasicAttribute( "description" );
287         Attributes attrs = new BasicAttributes( true );
288         attrs.put( attr );
289 
290         try
291         {
292             ctx.modifyAttributes( newRdn, DirContext.REMOVE_ATTRIBUTE, attrs );
293             fail( "Deletion of RDN attribute should fail." );
294         }
295         catch ( SchemaViolationException e )
296         {
297             // expected behaviour
298         }
299 
300         // Change RDN back to original
301         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
302         ctx.rename( newRdn, RDN );
303     }
304 
305 
306     /**
307      * Remove a an attribute which is not present on the entry, but in the
308      * schema.
309      * 
310      * Expected result: Deletion fails with NoSuchAttributeException
311      */
312     @Test
313     public void testRemoveAttributeNotPresent() throws Exception
314     {
315         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
316         
317         // Remove telephoneNumber Attribute
318         Attribute attr = new BasicAttribute( "telephoneNumber" );
319         Attributes attrs = new BasicAttributes( true );
320         attrs.put( attr );
321 
322         try
323         {
324             ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, attrs );
325             fail( "Deletion of attribute, which is not present in the entry, should fail." );
326         }
327         catch ( NoSuchAttributeException e )
328         {
329         	assertTrue( true );
330             // expected behaviour
331         }
332     }
333 
334 
335     /**
336      * Remove a an attribute value which is not present in the entry
337      * 
338      * Expected result: Deletion fails with NoSuchAttributeException
339      */
340     @Test
341     public void testRemoveAttributeValueNotPresent() throws Exception
342     {
343         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
344         
345         // Remove telephoneNumber Attribute
346         Attribute attr = new BasicAttribute( "telephoneNumber", "12345" );
347         Attributes attrs = new BasicAttributes( true );
348         attrs.put( attr );
349         
350         // Inject the new attribute
351         ctx.modifyAttributes( RDN, DirContext.ADD_ATTRIBUTE, attrs );
352 
353         // Now try to remove a value which is not present
354         Attribute attr2 = new BasicAttribute( "telephoneNumber", "7890" );
355         Attributes attrs2 = new BasicAttributes( true );
356         attrs2.put( attr2 );
357     	
358         ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, attrs2 );
359         
360         // We shopuld not get an exception
361         assertTrue( true );
362     }
363 
364 
365     /**
366      * Remove a an attribute which is not present in the schema.
367      * 
368      * Expected result: Deletion fails with NoSuchAttributeException
369      */
370     @Test
371     public void testRemoveAttributeNotValid() throws Exception
372     {
373         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
374         
375         // Remove phantasy attribute
376         Attribute attr = new BasicAttribute( "XXX" );
377         Attributes attrs = new BasicAttributes( true );
378         attrs.put( attr );
379 
380         try
381         {
382             ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, attrs );
383             fail( "Deletion of an invalid attribute should fail." );
384         }
385         catch ( NoSuchAttributeException e )
386         {
387             // expected behaviour
388         }
389         catch ( InvalidAttributeIdentifierException e )
390         {
391             // expected behaviour
392         }
393     }
394 
395 
396     /**
397      * Create a person entry and try to remove an attribute value
398      */
399     @Test
400     public void testReplaceNonExistingAttribute() throws Exception
401     {
402         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
403         
404         // Create an entry
405         Attributes attrs = getInetOrgPersonAttributes( "Bush", "Kate Bush" );
406         attrs.put( "givenname", "Kate" );
407         String rdn = "cn=Kate Bush";
408         ctx.createSubcontext( rdn, attrs );
409 
410         // replace attribute givenName with empty value (=> deletion)
411         Attribute attr = new BasicAttribute( "givenname" );
412         ModificationItem item = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr );
413         ctx.modifyAttributes( rdn, new ModificationItem[] { item } );
414 
415         SearchControls sctls = new SearchControls();
416         sctls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
417         String filter = "(cn=Kate Bush)";
418         String base = "";
419         NamingEnumeration<SearchResult> enm = ctx.search( base, filter, sctls );
420         if ( enm.hasMore() )
421         {
422             SearchResult sr = enm.next();
423             attrs = sr.getAttributes();
424             Attribute cn = sr.getAttributes().get( "cn" );
425             assertNotNull( cn );
426             assertTrue( cn.contains( "Kate Bush" ) );
427 
428             // Check whether attribute has been removed
429             Attribute givenName = sr.getAttributes().get( "givenname" );
430             assertNull( givenName );
431         }
432         else
433         {
434             fail( "entry not found" );
435         }
436 
437         ctx.destroySubcontext( rdn );
438     }
439 
440 
441     /**
442      * Create a person entry and try to remove an attribute value from the RDN
443      * by Replacement
444      */
445     @Test
446     public void testReplaceRdnByEmptyValueAttribute() throws Exception
447     {
448         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
449         
450         // Create an entry
451         Attributes attrs = getPersonAttributes( "Bush", "Kate Bush" );
452         String rdn = "cn=Kate Bush";
453         ctx.createSubcontext( rdn, attrs );
454 
455         // replace attribute cn with empty value (=> deletion)
456         Attribute attr = new BasicAttribute( "cn" );
457         ModificationItem item = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr );
458 
459         try
460         {
461             ctx.modifyAttributes( rdn, new ModificationItem[]
462                 { item } );
463             fail( "modify should fail" );
464         }
465         catch ( SchemaViolationException e )
466         {
467             // Expected behaviour
468         }
469 
470         ctx.destroySubcontext( rdn );
471     }
472 
473 
474     /**
475      * Create a person entry and try to remove an attribute from the RDN
476      */
477     @Test
478     public void testRemoveRdnAttribute() throws Exception
479     {
480         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
481         
482         // Create an entry
483         Attributes attrs = getPersonAttributes( "Bush", "Kate Bush" );
484         String rdn = "cn=Kate Bush";
485         ctx.createSubcontext( rdn, attrs );
486 
487         // replace attribute cn with empty value (=> deletion)
488         Attribute attr = new BasicAttribute( "cn" );
489         ModificationItem item = new ModificationItem( DirContext.REMOVE_ATTRIBUTE, attr );
490 
491         try
492         {
493             ctx.modifyAttributes( rdn, new ModificationItem[]
494                 { item } );
495             fail( "modify should fail" );
496         }
497         catch ( SchemaViolationException e )
498         {
499             // Expected behaviour
500         }
501 
502         ctx.destroySubcontext( rdn );
503     }
504 
505 
506     /**
507      * Create a person entry and try to remove an attribute from the RDN
508      */
509     @Test
510     public void testRemoveRdnAttributeValue() throws Exception
511     {
512         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
513         
514         // Create an entry
515         Attributes attrs = getPersonAttributes( "Bush", "Kate Bush" );
516         String rdn = "cn=Kate Bush";
517         ctx.createSubcontext( rdn, attrs );
518 
519         // replace attribute cn with empty value (=> deletion)
520         Attribute attr = new BasicAttribute( "cn", "Kate Bush" );
521         ModificationItem item = new ModificationItem( DirContext.REMOVE_ATTRIBUTE, attr );
522 
523         try
524         {
525             ctx.modifyAttributes( rdn, new ModificationItem[]
526                 { item } );
527             fail( "modify should fail" );
528         }
529         catch ( SchemaViolationException e )
530         {
531             // Expected behaviour
532         }
533 
534         ctx.destroySubcontext( rdn );
535     }
536 
537     
538     /**
539      * Create a person entry and try to remove objectClass attribute
540      */
541     @Test
542     public void testDeleteOclAttrWithTopPersonOrganizationalpersonInetorgperson() throws Exception 
543     {
544         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
545         
546         // Create an entry
547         Attributes attrs = getInetOrgPersonAttributes("Bush", "Kate Bush");
548         String rdn = "cn=Kate Bush";
549         ctx.createSubcontext(rdn, attrs);
550 
551         ModificationItem delModOp = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("objectclass", ""));
552 
553         try {
554             ctx.modifyAttributes(rdn, new ModificationItem[] { delModOp });
555             fail("deletion of objectclass should fail");
556         } catch (SchemaViolationException e) {
557             // expected
558         } catch (NoSuchAttributeException e) {
559             // expected
560         } catch (InvalidAttributeValueException e) {
561             // expected
562         } catch ( Exception e ) {
563             e.printStackTrace();
564         }
565 
566         ctx.destroySubcontext(rdn);
567     }
568 
569     
570     /**
571      * Create a person entry and try to remove objectClass attribute. A variant
572      * which works.
573      */
574     @Test
575     public void testDeleteOclAttrWithTopPersonOrganizationalpersonInetorgpersonVariant() throws Exception 
576     {
577         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
578         
579         // Create an entry
580         Attributes attrs = getInetOrgPersonAttributes("Bush", "Kate Bush");
581         String rdn = "cn=Kate Bush";
582         ctx.createSubcontext(rdn, attrs);
583 
584         ModificationItem delModOp = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("objectclass"));
585 
586         try {
587             ctx.modifyAttributes(rdn, new ModificationItem[] { delModOp });
588             fail("deletion of objectclass should fail");
589         } catch (SchemaViolationException e) {
590             // expected
591         }
592 
593         ctx.destroySubcontext(rdn);
594     }
595 }