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 java.util.Arrays;
24  
25  import javax.naming.NamingEnumeration;
26  import javax.naming.NamingException;
27  import javax.naming.NoPermissionException;
28  import javax.naming.directory.Attribute;
29  import javax.naming.directory.AttributeInUseException;
30  import javax.naming.directory.AttributeModificationException;
31  import javax.naming.directory.Attributes;
32  import javax.naming.directory.BasicAttribute;
33  import javax.naming.directory.BasicAttributes;
34  import javax.naming.directory.DirContext;
35  import javax.naming.directory.InvalidAttributeValueException;
36  import javax.naming.directory.ModificationItem;
37  import javax.naming.directory.NoSuchAttributeException;
38  import javax.naming.directory.SearchControls;
39  import javax.naming.directory.SearchResult;
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.integ.SiRunner;
45  import static org.apache.directory.server.integ.ServerIntegrationUtils.getWiredContext;
46  
47  import org.apache.directory.server.ldap.LdapService;
48  import org.junit.Test;
49  import org.junit.runner.RunWith;
50  import static org.junit.Assert.fail;
51  import static org.junit.Assert.assertTrue;
52  import static org.junit.Assert.assertFalse;
53  import static org.junit.Assert.assertEquals;
54  import static org.junit.Assert.assertNotNull;
55  
56  
57  /**
58   * Test case with different modify operations on a person entry. Each includes a
59   * single add op only. Created to demonstrate DIREVE-241 ("Adding an already
60   * existing attribute value with a modify operation does not cause an error.").
61   * 
62   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
63   * @version $Rev: 692919 $
64   */
65  @RunWith ( SiRunner.class ) 
66  @CleanupLevel ( Level.SUITE )
67  @ApplyLdifs( {
68      // Entry # 1
69      "dn: cn=Tori Amos,ou=system\n" +
70      "objectClass: inetOrgPerson\n" +
71      "objectClass: organizationalPerson\n" +
72      "objectClass: person\n" +
73      "objectClass: top\n" +
74      "description: an American singer-songwriter\n" +
75      "cn: Tori Amos\n" +
76      "sn: Amos\n\n" + 
77      // Entry # 2
78      "dn: cn=Debbie Harry,ou=system\n" +
79      "objectClass: inetOrgPerson\n" +
80      "objectClass: organizationalPerson\n" +
81      "objectClass: person\n" +
82      "objectClass: top\n" +
83      "cn: Debbie Harry\n" +
84      "sn: Harry\n\n" 
85      }
86  )
87  public class ModifyAddIT 
88  {
89      private static final String BASE = "ou=system";
90      private static final String RDN_TORI_AMOS = "cn=Tori Amos";
91      private static final String PERSON_DESCRIPTION = "an American singer-songwriter";
92      private static final String RDN_DEBBIE_HARRY = "cn=Debbie Harry";
93  
94      public static LdapService ldapService;
95      
96  
97      /**
98       * Creation of required attributes of a person entry.
99       */
100     protected Attributes getPersonAttributes( String sn, String cn )
101     {
102         Attributes attributes = new BasicAttributes( true );
103         Attribute attribute = new BasicAttribute( "objectClass" );
104         attribute.add( "top" );
105         attribute.add( "person" );
106         attribute.add( "organizationalperson" );
107         attribute.add( "inetorgperson" );
108         attributes.put( attribute );
109         attributes.put( "cn", cn );
110         attributes.put( "sn", sn );
111 
112         return attributes;
113     }
114 
115 
116     /**
117      * Add a new attribute to a person entry.
118      */
119     @Test
120     public void testAddNewAttributeValue() throws Exception
121     {
122         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
123         
124         // Add telephoneNumber attribute
125         String newValue = "1234567890";
126         Attributes attrs = new BasicAttributes( "telephoneNumber", newValue, true );
127         ctx.modifyAttributes( RDN_TORI_AMOS, DirContext.ADD_ATTRIBUTE, attrs );
128 
129         // Verify, that attribute value is added
130         attrs = ctx.getAttributes( RDN_TORI_AMOS );
131         Attribute attr = attrs.get( "telephoneNumber" );
132         assertNotNull( attr );
133         assertTrue( attr.contains( newValue ) );
134         assertEquals( 1, attr.size() );
135     }
136 
137 
138     /**
139      * Add a new attribute with two values.
140      */
141     @Test
142     public void testAddNewAttributeValues() throws Exception
143     {
144         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
145         
146         // Add telephoneNumber attribute
147         String[] newValues =
148             { "1234567890", "999999999" };
149         Attribute attr = new BasicAttribute( "telephoneNumber" );
150         attr.add( newValues[0] );
151         attr.add( newValues[1] );
152         Attributes attrs = new BasicAttributes( true );
153         attrs.put( attr );
154         ctx.modifyAttributes( RDN_TORI_AMOS, DirContext.ADD_ATTRIBUTE, attrs );
155 
156         // Verify, that attribute values are present
157         attrs = ctx.getAttributes( RDN_TORI_AMOS );
158         attr = attrs.get( "telephoneNumber" );
159         assertNotNull( attr );
160         assertTrue( attr.contains( newValues[0] ) );
161         assertTrue( attr.contains( newValues[1] ) );
162         assertEquals( newValues.length, attr.size() );
163     }
164 
165 
166     /**
167      * Add an additional value.
168      */
169     @Test
170     public void testAddAdditionalAttributeValue() throws Exception
171     {
172         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
173         
174         // A new description attribute value
175         String newValue = "A new description for this person";
176         assertFalse( newValue.equals( PERSON_DESCRIPTION ) );
177         Attributes attrs = new BasicAttributes( "description", newValue, true );
178 
179         ctx.modifyAttributes( RDN_TORI_AMOS, DirContext.ADD_ATTRIBUTE, attrs );
180 
181         // Verify, that attribute value is added
182         attrs = ctx.getAttributes( RDN_TORI_AMOS );
183         Attribute attr = attrs.get( "description" );
184         assertNotNull( attr );
185         assertTrue( attr.contains( newValue ) );
186         assertTrue( attr.contains( PERSON_DESCRIPTION ) );
187         assertEquals( 2, attr.size() );
188     }
189 
190 
191     /**
192      * Try to add an already existing attribute value.
193      * 
194      * Expected behaviour: Modify operation fails with an
195      * AttributeInUseException. Original LDAP Error code: 20 (Indicates that the
196      * attribute value specified in a modify or add operation already exists as
197      * a value for that attribute).
198      */
199     @Test
200     public void testAddExistingAttributeValue() throws Exception
201     {
202         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
203         
204         // Change description attribute
205         Attributes attrs = new BasicAttributes( "description", PERSON_DESCRIPTION, true );
206         
207         try
208         {
209             ctx.modifyAttributes( RDN_TORI_AMOS, DirContext.ADD_ATTRIBUTE, attrs );
210             fail( "Adding an already existing atribute value should fail." );
211         }
212         catch ( AttributeInUseException e )
213         {
214             // expected behaviour
215         }
216 
217         // Verify, that attribute is still there, and is the only one
218         attrs = ctx.getAttributes( RDN_TORI_AMOS );
219         Attribute attr = attrs.get( "description" );
220         assertNotNull( attr );
221         assertTrue( attr.contains( PERSON_DESCRIPTION ) );
222         assertEquals( 1, attr.size() );
223     }
224 
225     
226     /**
227      * Try to add an already existing attribute value.
228      * 
229      * Expected behaviour: Modify operation fails with an
230      * AttributeInUseException. Original LDAP Error code: 20 (Indicates that the
231      * attribute value specified in a modify or add operation already exists as
232      * a value for that attribute).
233      * 
234      * Check for bug DIR_SERVER664
235      */
236     @Test
237     public void testAddExistingNthAttributesDirServer664() throws Exception
238     {
239         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
240         
241         // Change description attribute
242         Attributes attrs = new BasicAttributes( true );
243         attrs.put( new BasicAttribute( "telephoneNumber", "attr 1" ) );
244         attrs.put( new BasicAttribute( "telephoneNumber", "attr 2" ) );
245         attrs.put( new BasicAttribute( "telephoneNumber", "attr 3" ) );
246         attrs.put( new BasicAttribute( "telephoneNumber", "attr 4" ) );
247         attrs.put( new BasicAttribute( "telephoneNumber", "attr 5" ) );
248         attrs.put( new BasicAttribute( "telephoneNumber", "attr 6" ) );
249         attrs.put( new BasicAttribute( "telephoneNumber", "attr 7" ) );
250         attrs.put( new BasicAttribute( "telephoneNumber", "attr 8" ) );
251         attrs.put( new BasicAttribute( "telephoneNumber", "attr 9" ) );
252         attrs.put( new BasicAttribute( "telephoneNumber", "attr 10" ) );
253         attrs.put( new BasicAttribute( "telephoneNumber", "attr 11" ) );
254         attrs.put( new BasicAttribute( "telephoneNumber", "attr 12" ) );
255         attrs.put( new BasicAttribute( "telephoneNumber", "attr 13" ) );
256         attrs.put( new BasicAttribute( "telephoneNumber", "attr 14" ) );
257         
258         Attribute attr = new BasicAttribute( "description", PERSON_DESCRIPTION );
259 
260         attrs.put( attr );
261         
262         try
263         {
264             ctx.modifyAttributes( RDN_TORI_AMOS, DirContext.ADD_ATTRIBUTE, attrs );
265             fail( "Adding an already existing atribute value should fail." );
266         }
267         catch ( AttributeInUseException e )
268         {
269             // expected behaviour
270         }
271 
272         // Verify, that attribute is still there, and is the only one
273         attrs = ctx.getAttributes( RDN_TORI_AMOS );
274         attr = attrs.get( "description" );
275         assertNotNull( attr );
276         assertTrue( attr.contains( PERSON_DESCRIPTION ) );
277         assertEquals( 1, attr.size() );
278     }
279 
280     
281     /**
282      * Check for DIR_SERVER_643
283      */
284     @Test
285     public void testTwoDescriptionDirServer643() throws Exception
286     {
287         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
288         
289         // Change description attribute
290         Attributes attrs = new BasicAttributes( true );
291         Attribute attr = new BasicAttribute( "description", "a British singer-songwriter with an expressive four-octave voice" );
292         attr.add( "one of the most influential female artists of the twentieth century" );
293         attrs.put( attr );
294         
295         ctx.modifyAttributes( RDN_TORI_AMOS, DirContext.ADD_ATTRIBUTE, attrs );
296 
297         // Verify, that attribute is still there, and is the only one
298         attrs = ctx.getAttributes( RDN_TORI_AMOS );
299         attr = attrs.get( "description" );
300         assertNotNull( attr );
301         assertEquals( 3, attr.size() );
302         assertTrue( attr.contains( "a British singer-songwriter with an expressive four-octave voice" ) );
303         assertTrue( attr.contains( "one of the most influential female artists of the twentieth century" ) );
304         assertTrue( attr.contains( PERSON_DESCRIPTION ) );
305     }
306 
307     /**
308      * Try to add a duplicate attribute value to an entry, where this attribute
309      * is already present (objectclass in this case). Expected behaviour is that
310      * the modify operation causes an error (error code 20, "Attribute or value
311      * exists").
312      */
313     @Test
314     public void testAddDuplicateValueToExistingAttribute() throws Exception
315     {
316         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
317         
318         // modify object classes, add a new value twice
319         Attribute ocls = new BasicAttribute( "objectClass", "organizationalPerson" );
320         ModificationItem[] modItems = new ModificationItem[2];
321         modItems[0] = new ModificationItem( DirContext.ADD_ATTRIBUTE, ocls );
322         modItems[1] = new ModificationItem( DirContext.ADD_ATTRIBUTE, ocls );
323         try
324         {
325             ctx.modifyAttributes( RDN_TORI_AMOS, modItems );
326             fail( "Adding a duplicate attribute value should cause an error." );
327         }
328         catch ( AttributeInUseException ex )
329         {
330         }
331 
332         // Check, whether attribute objectClass is unchanged
333         Attributes attrs = ctx.getAttributes( RDN_TORI_AMOS );
334         ocls = attrs.get( "objectClass" );
335         assertEquals( ocls.size(), 4 );
336         assertTrue( ocls.contains( "top" ) );
337         assertTrue( ocls.contains( "person" ) );
338     }
339 
340 
341     /**
342      * Try to add a duplicate attribute value to an entry, where this attribute
343      * is not present. Expected behaviour is that the modify operation causes an
344      * error (error code 20, "Attribute or value exists").
345      */
346     @Test
347     public void testAddDuplicateValueToNewAttribute() throws Exception
348     {
349         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
350         
351         // add the same description value twice
352         Attribute desc = new BasicAttribute( "description", "another description value besides songwriter" );
353         ModificationItem[] modItems = new ModificationItem[2];
354         modItems[0] = new ModificationItem( DirContext.ADD_ATTRIBUTE, desc );
355         modItems[1] = new ModificationItem( DirContext.ADD_ATTRIBUTE, desc );
356         try
357         {
358             ctx.modifyAttributes( RDN_TORI_AMOS, modItems );
359             fail( "Adding a duplicate attribute value should cause an error." );
360         }
361         catch ( AttributeInUseException ex )
362         {
363         }
364 
365         // Check, whether attribute description is still not present
366         Attributes attrs = ctx.getAttributes( RDN_TORI_AMOS );
367         assertEquals( 1, attrs.get( "description" ).size() );
368     }
369 
370 
371     /**
372      * Modify the entry with a bad attribute : this should fail 
373      */
374     @Test
375     public void testSearchBadAttribute() throws Exception
376     {
377         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
378         
379         // Add a not existing attribute
380         String newValue = "unbelievable";
381         Attributes attrs = new BasicAttributes( "voice", newValue, true );
382 
383         try
384         {
385             ctx.modifyAttributes( RDN_TORI_AMOS, DirContext.ADD_ATTRIBUTE, attrs );
386         }
387         catch ( NoSuchAttributeException nsae )
388         {
389             // We have a failure : the attribute is unknown in the schema
390             assertTrue( true );
391             return;
392         }
393 
394         fail( "Cannot reach this point" );
395     }
396     
397     
398     /**
399      * Create a person entry and perform a modify op, in which
400      * we modify an attribute two times.
401      */
402     @Test
403     public void testAttributeValueMultiMofificationDIRSERVER_636() throws Exception 
404     {
405         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
406         
407         // Create a person entry
408         Attributes attrs = getPersonAttributes("Bush", "Kate Bush");
409         String rdn = "cn=Kate Bush";
410         ctx.createSubcontext(rdn, attrs);
411 
412         // Add a description with two values
413         String[] descriptions = {
414                 "Kate Bush is a British singer-songwriter.",
415                 "She has become one of the most influential female artists of the twentieth century." };
416         Attribute desc1 = new BasicAttribute("description");
417         desc1.add(descriptions[0]);
418         desc1.add(descriptions[1]);
419 
420         ModificationItem addModOp = new ModificationItem(
421                 DirContext.ADD_ATTRIBUTE, desc1);
422 
423         Attribute desc2 = new BasicAttribute("description");
424         desc2.add(descriptions[1]);
425         ModificationItem delModOp = new ModificationItem(
426                 DirContext.REMOVE_ATTRIBUTE, desc2);
427 
428         ctx.modifyAttributes(rdn, new ModificationItem[] { addModOp,
429                         delModOp });
430 
431         SearchControls sctls = new SearchControls();
432         sctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
433         String filter = "(cn=*Bush)";
434         String base = "";
435 
436         // Check entry
437         NamingEnumeration<SearchResult> enm = ctx.search(base, filter, sctls);
438         assertTrue(enm.hasMore());
439         
440         while (enm.hasMore()) {
441             SearchResult sr = enm.next();
442             attrs = sr.getAttributes();
443             Attribute desc = sr.getAttributes().get("description");
444             assertEquals(1, desc.size());
445             assertTrue(desc.contains(descriptions[0]));
446         }
447 
448         // Remove the person entry
449         ctx.destroySubcontext(rdn);
450     }
451 
452     
453     /**
454      * Try to add subschemaSubentry attribute to an entry
455      */
456     @Test
457     public void testModifyOperationalAttributeAdd() throws Exception
458     {
459         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
460         
461         ModificationItem modifyOp = new ModificationItem( DirContext.ADD_ATTRIBUTE, new BasicAttribute(
462             "subschemaSubentry", "cn=anotherSchema" ) );
463 
464         try
465         {
466             ctx.modifyAttributes( RDN_DEBBIE_HARRY, new ModificationItem[]
467                 { modifyOp } );
468 
469             fail( "modification of entry should fail" );
470         }
471         catch ( InvalidAttributeValueException e )
472         {
473             // Expected result
474         }
475         catch ( NoPermissionException e )
476         {
477             // Expected result
478         }
479     }
480 
481 
482     /**
483      * Create a person entry and perform a modify op on an
484      * attribute which is part of the DN. This is not allowed.
485      * 
486      * A JIRA has been created for this bug : DIRSERVER_687
487      */
488     @Test
489      public void testDNAttributeMemberMofificationDIRSERVER_687() throws Exception 
490      {
491          DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
492          
493         // Create a person entry
494         Attributes attrs = getPersonAttributes("Bush", "Kate Bush");
495         String rdn = "cn=Kate Bush";
496         ctx.createSubcontext(rdn, attrs);
497 
498         // Try to modify the cn attribute
499         Attribute desc1 = new BasicAttribute( "cn", "Georges Bush" );
500 
501         ModificationItem addModOp = new ModificationItem(
502                 DirContext.REPLACE_ATTRIBUTE, desc1);
503 
504         try
505         {
506             ctx.modifyAttributes( rdn, new ModificationItem[] { addModOp } );
507             fail();
508         }
509         catch ( AttributeModificationException ame )
510         {
511             assertTrue( true );
512             // Remove the person entry
513             ctx.destroySubcontext(rdn);
514         }
515         catch ( NamingException ne ) 
516         {
517             assertTrue( true );
518             // Remove the person entry
519             ctx.destroySubcontext(rdn);
520         }
521     }
522 
523     
524     /**
525      * Try to modify an entry adding invalid number of values for a single-valued atribute
526      * 
527      * @see <a href="http://issues.apache.org/jira/browse/DIRSERVER-614">DIRSERVER-614</a>
528      */
529     @Test
530     public void testModifyAddWithInvalidNumberOfAttributeValues() throws Exception
531     {
532         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
533         
534         Attributes attrs = new BasicAttributes( true );
535         Attribute ocls = new BasicAttribute( "objectClass" );
536         ocls.add( "top" );
537         ocls.add( "inetOrgPerson" );
538         attrs.put( ocls );
539         attrs.put( "cn", "Fiona Apple" );
540         attrs.put( "sn", "Apple" );
541         ctx.createSubcontext( "cn=Fiona Apple", attrs );
542         
543         // add two displayNames to an inetOrgPerson
544         attrs = new BasicAttributes( true );
545         Attribute displayName = new BasicAttribute( "displayName" );
546         displayName.add( "Fiona" );
547         displayName.add( "Fiona A." );
548         attrs.put( displayName );
549         
550         try
551         {
552             ctx.modifyAttributes( "cn=Fiona Apple", DirContext.ADD_ATTRIBUTE, attrs );
553             fail( "modification of entry should fail" );
554         }
555         catch ( InvalidAttributeValueException e )
556         {
557             
558         }
559     }
560 
561 
562     /**
563      * Add a new attribute to a person entry.
564      */
565     @Test
566     public void testAddNewBinaryAttributeValue() throws Exception
567     {
568         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
569         
570         // Add a binary attribute
571         byte[] newValue = new byte[]{0x00, 0x01, 0x02, 0x03};
572         Attributes attrs = new BasicAttributes( "userCertificate;binary", newValue, true );
573         ctx.modifyAttributes( RDN_TORI_AMOS, DirContext.ADD_ATTRIBUTE, attrs );
574 
575         // Verify, that attribute value is added
576         attrs = ctx.getAttributes( RDN_TORI_AMOS );
577         Attribute attr = attrs.get( "userCertificate" );
578         assertNotNull( attr );
579         assertTrue( attr.contains( newValue ) );
580         byte[] certificate = (byte[])attr.get();
581         assertTrue( Arrays.equals( newValue, certificate ) );
582         assertEquals( 1, attr.size() );
583     }
584 }