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.modifydn;
21  
22  
23  import javax.naming.NameNotFoundException;
24  import javax.naming.NamingEnumeration;
25  import javax.naming.NamingException;
26  import javax.naming.NoPermissionException;
27  import javax.naming.directory.Attribute;
28  import javax.naming.directory.Attributes;
29  import javax.naming.directory.BasicAttribute;
30  import javax.naming.directory.BasicAttributes;
31  import javax.naming.directory.DirContext;
32  import javax.naming.directory.SchemaViolationException;
33  import javax.naming.directory.SearchControls;
34  import javax.naming.directory.SearchResult;
35  
36  import org.apache.directory.server.core.integ.Level;
37  import org.apache.directory.server.core.integ.annotations.CleanupLevel;
38  import org.apache.directory.server.integ.SiRunner;
39  import org.apache.directory.server.ldap.LdapService;
40  import org.junit.Ignore;
41  import org.junit.Test;
42  import org.junit.runner.RunWith;
43  
44  import static org.apache.directory.server.integ.ServerIntegrationUtils.getWiredContext;
45  import static org.junit.Assert.assertEquals;
46  import static org.junit.Assert.assertNotNull;
47  import static org.junit.Assert.assertTrue;
48  import static org.junit.Assert.assertNull;
49  import static org.junit.Assert.fail;
50  
51  
52  /**
53   * Test case with different modify DN operations on a person entry.
54   * Originally created to demonstrate DIREVE-173.
55   * 
56   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
57   * @version $Rev: 679049 $
58   */
59  @RunWith ( SiRunner.class ) 
60  @CleanupLevel ( Level.SUITE )
61  public class ModifyRdnIT 
62  {
63      private static final String BASE = "ou=system";
64  
65      public static LdapService ldapService;
66      
67      
68      /**
69       * Create attributes for a person entry.
70       */
71      private Attributes getPersonAttributes( String sn, String cn )
72      {
73          Attributes attributes = new BasicAttributes( true );
74          Attribute attribute = new BasicAttribute( "objectClass" );
75          attribute.add( "top" );
76          attribute.add( "person" );
77          attributes.put( attribute );
78          attributes.put( "cn", cn );
79          attributes.put( "sn", sn );
80          attributes.put( "description", cn + " is a person." );
81  
82          return attributes;
83      }
84  
85  
86      /**
87       * Create attributes for a organizational unit entry.
88       */
89      private Attributes getOrganizationalUnitAttributes( String ou )
90      {
91          Attributes attributes = new BasicAttributes( true );
92          Attribute attribute = new BasicAttribute( "objectClass" );
93          attribute.add( "top" );
94          attribute.add( "organizationalUnit" );
95          attributes.put( attribute );
96          attributes.put( "ou", ou );
97          attributes.put( "description", ou + " is an organizational unit." );
98  
99          return attributes;
100     }
101 
102 
103     /**
104      * Modify Rdn of an entry, delete its old rdn value.
105      */
106     @Test
107     public void testModifyRdnAndDeleteOld() throws Exception
108     {
109         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
110         
111         // Create a person, cn value is rdn
112         String oldCn = "Myra Ellen Amos";
113         String oldRdn = "cn=" + oldCn;
114         Attributes attributes = this.getPersonAttributes( "Amos", oldCn );
115         ctx.createSubcontext( oldRdn, attributes );
116 
117         // modify Rdn
118         String newCn = "Tori Amos";
119         String newRdn = "cn=" + newCn;
120         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
121         ctx.rename( oldRdn, newRdn );
122 
123         // Check, whether old Entry does not exists
124         try
125         {
126             ctx.lookup( oldRdn );
127             fail( "Entry must not exist" );
128         }
129         catch ( NameNotFoundException ignored )
130         {
131             // expected behaviour
132             assertTrue( true );
133         }
134 
135         // Check, whether new Entry exists
136         DirContext tori = ( DirContext ) ctx.lookup( newRdn );
137         assertNotNull( tori );
138 
139         // Check values of cn
140         Attribute cn = tori.getAttributes( "" ).get( "cn" );
141         assertTrue( cn.contains( newCn ) );
142         assertTrue( !cn.contains( oldCn ) ); // old value is gone
143         assertEquals( 1, cn.size() );
144 
145         // Remove entry (use new rdn)
146         ctx.unbind( newRdn );
147     }
148 
149 
150     /**
151      * Modify Rdn of an entry, without deleting its old rdn value.
152      * 
153      * The JNDI property is set with 'False'
154      */
155     @Test
156     public void testModifyRdnAndDontDeleteOldFalse() throws Exception
157     {
158         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
159         
160         // Create a person, cn value is rdn
161         String oldCn = "Myra Ellen Amos";
162         String oldRdn = "cn=" + oldCn;
163         Attributes attributes = this.getPersonAttributes( "Amos", oldCn );
164         ctx.createSubcontext( oldRdn, attributes );
165 
166         // modify Rdn
167         String newCn = "Tori Amos";
168         String newRdn = "cn=" + newCn;
169         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "False" );
170         ctx.rename( oldRdn, newRdn );
171 
172         // Check, whether old Entry does not exists
173         try
174         {
175             ctx.lookup( oldRdn );
176             fail( "Entry must not exist" );
177         }
178         catch ( NameNotFoundException ignored )
179         {
180             // expected behaviour
181             assertTrue( true );
182         }
183 
184         // Check, whether new Entry exists
185         DirContext tori = ( DirContext ) ctx.lookup( newRdn );
186         assertNotNull( tori );
187 
188         // Check values of cn
189         Attribute cn = tori.getAttributes( "" ).get( "cn" );
190         assertTrue( cn.contains( newCn ) );
191         assertTrue( cn.contains( oldCn ) ); // old value is still there
192         assertEquals( 2, cn.size() );
193 
194         // Remove entry (use new rdn)
195         ctx.unbind( newRdn );
196     }
197 
198 
199     /**
200      * Modify Rdn of an entry, keep its old rdn value.
201      */
202     @Test
203     public void testModifyRdnAndKeepOld() throws Exception
204     {
205         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
206         
207         // Create a person, cn value is rdn
208         String oldCn = "Myra Ellen Amos";
209         String oldRdn = "cn=" + oldCn;
210         Attributes attributes = this.getPersonAttributes( "Amos", oldCn );
211         ctx.createSubcontext( oldRdn, attributes );
212 
213         // modify Rdn
214         String newCn = "Tori Amos";
215         String newRdn = "cn=" + newCn;
216         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
217         ctx.rename( oldRdn, newRdn );
218 
219         // Check, whether old entry does not exist
220         try
221         {
222             ctx.lookup( oldRdn );
223             fail( "Entry must not exist" );
224         }
225         catch ( NameNotFoundException ignored )
226         {
227             // expected behaviour
228             assertTrue( true );
229         }
230 
231         // Check, whether new entry exists
232         DirContext tori = ( DirContext ) ctx.lookup( newRdn );
233         assertNotNull( tori );
234 
235         // Check values of cn
236         Attribute cn = tori.getAttributes( "" ).get( "cn" );
237         assertTrue( cn.contains( newCn ) );
238         assertTrue( cn.contains( oldCn ) ); // old value is still there
239         assertEquals( 2, cn.size() );
240 
241         // Remove entry (use new rdn)
242         ctx.unbind( newRdn );
243     }
244 
245 
246     /**
247      * Modify Rdn of an entry, delete its old rdn value. Here, the rdn attribute
248      * cn has another value as well.
249      */
250     @Test
251     public void testModifyRdnAndDeleteOldVariant() throws Exception
252     {
253         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
254         
255         // Create a person, cn value is rdn
256         String oldCn = "Myra Ellen Amos";
257         String oldRdn = "cn=" + oldCn;
258         Attributes attributes = this.getPersonAttributes( "Amos", oldCn );
259 
260         // add a second cn value
261         String alternateCn = "Myra E. Amos";
262         Attribute cn = attributes.get( "cn" );
263         cn.add( alternateCn );
264         assertEquals( 2, cn.size() );
265 
266         ctx.createSubcontext( oldRdn, attributes );
267 
268         // modify Rdn
269         String newCn = "Tori Amos";
270         String newRdn = "cn=" + newCn;
271         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
272         ctx.rename( oldRdn, newRdn );
273 
274         // Check, whether old Entry does not exist anymore
275         try
276         {
277             ctx.lookup( oldRdn );
278             fail( "Entry must not exist" );
279         }
280         catch ( NameNotFoundException ignored )
281         {
282             // expected behaviour
283             assertTrue( true );
284         }
285 
286         // Check, whether new Entry exists
287         DirContext tori = ( DirContext ) ctx.lookup( newRdn );
288         assertNotNull( tori );
289 
290         // Check values of cn
291         cn = tori.getAttributes( "" ).get( "cn" );
292         assertTrue( cn.contains( newCn ) );
293         assertTrue( !cn.contains( oldCn ) ); // old value is gone
294         assertTrue( cn.contains( alternateCn ) ); // alternate value is still available
295         assertEquals( 2, cn.size() );
296 
297         // Remove entry (use new rdn)
298         ctx.unbind( newRdn );
299     }
300 
301 
302     /**
303      * Modify DN of an entry, changing RDN from cn to sn.
304      */
305     @Test
306     public void testModifyRdnDifferentAttribute() throws Exception
307     {
308         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
309         
310         // Create a person, cn value is rdn
311         String cnVal = "Tori Amos";
312         String snVal = "Amos";
313         String oldRdn = "cn=" + cnVal;
314         Attributes attributes = this.getPersonAttributes( snVal, cnVal );
315         ctx.createSubcontext( oldRdn, attributes );
316 
317         // modify Rdn from cn=... to sn=...
318         String newRdn = "sn=" + snVal;
319         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
320         ctx.rename( oldRdn, newRdn );
321 
322         // Check, whether old Entry does not exists
323         try
324         {
325             ctx.lookup( oldRdn );
326             fail( "Entry must not exist" );
327         }
328         catch ( NameNotFoundException ignored )
329         {
330             // expected behaviour
331         }
332 
333         // Check, whether new Entry exists
334         DirContext tori = ( DirContext ) ctx.lookup( newRdn );
335         assertNotNull( tori );
336 
337         // Check values of cn and sn
338         // especially the number of cn and sn occurences
339         Attribute cn = tori.getAttributes( "" ).get( "cn" );
340         assertTrue( cn.contains( cnVal ) );
341         assertEquals( "Number of cn occurences", 1, cn.size() );
342         Attribute sn = tori.getAttributes( "" ).get( "sn" );
343         assertTrue( sn.contains( snVal ) );
344         assertEquals( "Number of sn occurences", 1, sn.size() );
345 
346         // Remove entry (use new rdn)
347         ctx.unbind( newRdn );
348     }
349 
350 
351     /**
352      * Modify DN of an entry, changing RDN from cn to sn, 
353      * delete old RDn, must fail because cn can not be deleted.
354      */
355     @Test
356     public void testModifyRdnDifferentAttributeDeleteOldFails() throws Exception
357     {
358         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
359         
360         // Create a person, cn value is rdn
361         String cnVal = "Tori Amos";
362         String snVal = "Amos";
363         String oldRdn = "cn=" + cnVal;
364         Attributes attributes = this.getPersonAttributes( snVal, cnVal );
365         ctx.createSubcontext( oldRdn, attributes );
366 
367         // modify Rdn from cn=... to sn=...
368         String newRdn = "sn=" + snVal;
369         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
370         try
371         {
372             ctx.rename( oldRdn, newRdn );
373             fail( "Rename must fail, mandatory attirbute cn can not be deleted." );
374         }
375         catch ( SchemaViolationException ignored )
376         {
377             // expected behaviour
378         }
379 
380         // Remove entry (use old rdn)
381         ctx.unbind( oldRdn );
382     }
383 
384 
385     /**
386      * Test for DIRSERVER-1086.
387      * Modify Rdn of an entry that has a child entry, delete its old rdn value.
388      * Ensure that the tree is not broken.
389      */
390     @Test
391     public void testModifyRdnAndDeleteOldWithChild() throws Exception
392     {
393         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
394         
395         // Create an organizational unit, ou value is rdn
396         String oldOu = "Writers";
397         String oldRdn = "ou=" + oldOu;
398         Attributes attributes = this.getOrganizationalUnitAttributes( oldOu );
399         DirContext createdCtx = ctx.createSubcontext( oldRdn, attributes );
400 
401         // Create a child
402         String childCn = "Tori Amos";
403         String childRdn = "cn=" + childCn;
404         Attributes childAttributes = this.getPersonAttributes( "Amos", childCn );
405         createdCtx.createSubcontext( childRdn, childAttributes );
406 
407         // modify Rdn
408         String newOu = "Singers";
409         String newRdn = "ou=" + newOu;
410         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
411         ctx.rename( oldRdn, newRdn );
412 
413         // Check, whether old Entry does not exists
414         try
415         {
416             ctx.lookup( oldRdn );
417             fail( "Entry must not exist" );
418         }
419         catch ( NameNotFoundException ignored )
420         {
421             // expected behaviour
422             assertTrue( true );
423         }
424 
425         // Check, whether new Entry exists
426         DirContext org = ( DirContext ) ctx.lookup( newRdn );
427         assertNotNull( org );
428 
429         // Check values of ou
430         Attribute ou = org.getAttributes( "" ).get( "ou" );
431         assertTrue( ou.contains( newOu ) );
432         assertTrue( !ou.contains( oldOu ) ); // old value is gone
433         assertEquals( 1, ou.size() );
434 
435         // Perform a search under renamed ou and check whether exactly one child entry exist
436         SearchControls searchControls = new SearchControls();
437         searchControls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
438         searchControls.setReturningAttributes( new String[]
439             { "objectClass" } );
440         NamingEnumeration<SearchResult> results = org.search( "", "(objectClass=*)", searchControls );
441         assertTrue( results.hasMore() );
442         results.next();
443         assertTrue( !results.hasMore() );
444 
445         // Check whether Tori exists
446         DirContext tori = ( DirContext ) org.lookup( childRdn );
447         assertNotNull( tori );
448 
449         // Remove entry (use new rdn)
450         ctx.unbind( childRdn + "," + newRdn );
451         ctx.unbind( newRdn );
452     }
453 
454 
455     /**
456      * Test for DIRSERVER-1096.
457      * Modify the RDN of an entry with an escaped new RDN. 
458      * Ensure that the attribute itself contains the unescaped value.
459      */
460     @Test
461     public void testModifyRdnWithEncodedNewRdn() throws Exception
462     {
463         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
464         
465         // Create a person "cn=Tori Amos", cn value is rdn
466         String cnVal = "Tori Amos";
467         String snVal = "Amos";
468         String oldRdn = "cn=" + cnVal;
469         Attributes attributes = this.getPersonAttributes( snVal, cnVal );
470         ctx.createSubcontext( oldRdn, attributes );
471 
472         // modify Rdn from cn=Tori Amos to cn=<a Umlaut>\+
473         String newCnEscapedVal = new String( new byte[]
474             { ( byte ) 0xC3, ( byte ) 0xA4, '\\', '+' }, "UTF-8" );
475         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
476         String newRdn = "cn=" + newCnEscapedVal;
477         ctx.rename( oldRdn, newRdn );
478 
479         // Check, whether old Entry does not exists
480         try
481         {
482             ctx.lookup( oldRdn );
483             fail( "Entry must not exist" );
484         }
485         catch ( NameNotFoundException ignored )
486         {
487             // expected behaviour
488         }
489 
490         // Check, whether new Entry exists
491         DirContext newCtx = ( DirContext ) ctx.lookup( newRdn );
492         assertNotNull( newCtx );
493 
494         // Check that the DN contains the escaped value
495         assertEquals( "cn=" + newCnEscapedVal + "," + ctx.getNameInNamespace(), newCtx.getNameInNamespace() );
496 
497         // Check that cn contains the unescaped value
498         Attribute cn = newCtx.getAttributes( "" ).get( "cn" );
499         assertEquals( "Number of cn occurences", 1, cn.size() );
500         String expectedCn = new String( new byte[] { ( byte ) 0xC3, ( byte ) 0xA4, '+' }, "UTF-8" );
501         assertTrue( cn.contains( expectedCn ) );
502 
503         // Remove entry (use new rdn)
504         ctx.unbind( newRdn );
505     }
506 
507 
508     /**
509      * Test for DIRSERVER-1096.
510      * Modify the RDN of an entry with an escaped new RDN. 
511      * Ensure that the attribute itself contains the unescaped value.
512      */
513     @Test
514     public void testModifyRdnWithEscapedPoundNewRdn() throws Exception
515     {
516         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
517         
518         // Create a person "cn=Tori Amos", cn value is rdn
519         String cnVal = "Tori Amos";
520         String snVal = "Amos";
521         String oldRdn = "cn=" + cnVal;
522         Attributes attributes = this.getPersonAttributes( snVal, cnVal );
523         ctx.createSubcontext( oldRdn, attributes );
524 
525         // modify Rdn from cn=Tori Amos to cn=\#test\+
526         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
527         String newRdn = "cn=\\23test";
528         ctx.rename( oldRdn, newRdn );
529 
530         // Check, whether old Entry does not exists
531         try
532         {
533             ctx.lookup( oldRdn );
534             fail( "Entry must not exist" );
535         }
536         catch ( NameNotFoundException ignored )
537         {
538             // expected behaviour
539         }
540 
541         // Check, whether new Entry exists
542         DirContext newCtx = ( DirContext ) ctx.lookup( newRdn );
543         assertNotNull( newCtx );
544 
545         // Check that the DN contains the escaped value
546         assertEquals( "cn=\\23test," + ctx.getNameInNamespace(), newCtx.getNameInNamespace() );
547 
548         // Check that cn contains the unescaped value
549         Attribute cn = newCtx.getAttributes( "" ).get( "cn" );
550         assertEquals( "Number of cn occurences", 1, cn.size() );
551         assertTrue( cn.contains( "#test" ) );
552 
553         // Remove entry (use new rdn)
554         ctx.unbind( newRdn );
555     }
556 
557 
558     /**
559      * Test for DIRSERVER-1162 and DIRSERVER-1085.
560      * 
561      * Modify single valued RDN to a multi valued RDN.
562      * - Old Rdn: cn
563      * - New Rdn: cn+sn
564      * - Keep old Rdn
565      * - Attributes: cn, sn, description must exist 
566      */
567     @Test
568     public void testModifyMultiValuedRdnVariant1() throws Exception
569     {
570         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
571         
572         Attributes attributes = createPerson( "cn" );
573         String oldRdn = getRdn( attributes, "cn" );
574         String newRdn = getRdn( attributes, "cn", "sn" );
575 
576         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
577         ctx.rename( oldRdn, newRdn );
578 
579         // Check whether new Entry exists
580         DirContext newCtx = ( DirContext ) ctx.lookup( newRdn );
581         assertNotNull( newCtx );
582 
583         // Check attributes
584         Attribute cnAttr = newCtx.getAttributes( "" ).get( "cn" );
585         assertEquals( 1, cnAttr.size() );
586         assertTrue( cnAttr.contains( "Tori Amos" ) );
587         Attribute snAttr = newCtx.getAttributes( "" ).get( "sn" );
588         assertEquals( 1, snAttr.size() );
589         assertTrue( snAttr.contains( "Amos" ) );
590         Attribute descriptionAttr = newCtx.getAttributes( "" ).get( "description" );
591         assertEquals( 1, descriptionAttr.size() );
592 
593         // Remove entry (use new rdn)
594         ctx.unbind( newRdn );
595     }
596 
597 
598     /**
599      * Test for DIRSERVER-1162 and DIRSERVER-1085.
600      * 
601      * Modify single valued RDN to a multi valued RDN.
602      * - Old Rdn: cn
603      * - New Rdn: cn+sn
604      * - Delete old Rdn
605      * - Attributes: cn, sn, description must exist 
606      */
607     @Test
608     public void testModifyMultiValuedRdnVariant2() throws Exception
609     {
610         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
611         
612         Attributes attributes = createPerson( "cn" );
613         String oldRdn = getRdn( attributes, "cn" );
614         String newRdn = getRdn( attributes, "cn", "sn" );
615 
616         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
617         ctx.rename( oldRdn, newRdn );
618 
619         // Check whether new Entry exists
620         DirContext newCtx = ( DirContext ) ctx.lookup( newRdn );
621         assertNotNull( newCtx );
622 
623         // Check attributes
624         Attribute cnAttr = newCtx.getAttributes( "" ).get( "cn" );
625         assertEquals( 1, cnAttr.size() );
626         assertTrue( cnAttr.contains( "Tori Amos" ) );
627         Attribute snAttr = newCtx.getAttributes( "" ).get( "sn" );
628         assertEquals( 1, snAttr.size() );
629         assertTrue( snAttr.contains( "Amos" ) );
630         Attribute descriptionAttr = newCtx.getAttributes( "" ).get( "description" );
631         assertEquals( 1, descriptionAttr.size() );
632 
633         // Remove entry (use new rdn)
634         ctx.unbind( newRdn );
635     }
636 
637 
638     /**
639      * Test for DIRSERVER-1162 and DIRSERVER-1085.
640      * 
641      * Modify single valued RDN to a multi valued RDN.
642      * - Old Rdn: description
643      * - New Rdn: cn+sn
644      * - Keep old Rdn
645      * - Attributes: cn, sn, description must exist 
646      */
647     @Test
648     public void testModifyMultiValuedRdnVariant3() throws Exception
649     {
650         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
651         
652         Attributes attributes = createPerson( "description" );
653         String oldRdn = getRdn( attributes, "description" );
654         String newRdn = getRdn( attributes, "cn", "sn" );
655 
656         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
657         ctx.rename( oldRdn, newRdn );
658 
659         // Check whether new Entry exists
660         DirContext newCtx = ( DirContext ) ctx.lookup( newRdn );
661         assertNotNull( newCtx );
662 
663         // Check attributes
664         Attribute cnAttr = newCtx.getAttributes( "" ).get( "cn" );
665         assertEquals( 1, cnAttr.size() );
666         assertTrue( cnAttr.contains( "Tori Amos" ) );
667         Attribute snAttr = newCtx.getAttributes( "" ).get( "sn" );
668         assertEquals( 1, snAttr.size() );
669         assertTrue( snAttr.contains( "Amos" ) );
670         Attribute descriptionAttr = newCtx.getAttributes( "" ).get( "description" );
671         assertEquals( 1, descriptionAttr.size() );
672 
673         // Remove entry (use new rdn)
674         ctx.unbind( newRdn );
675     }
676 
677 
678     /**
679      * Test for DIRSERVER-1162 and DIRSERVER-1085.
680      * 
681      * Modify single valued RDN to a multi valued RDN.
682      * - Old Rdn: description
683      * - New Rdn: cn+sn
684      * - Delete old Rdn
685      * - Attributes: cn, sn must exist; descriptions must not exist 
686      */
687     @Test
688     public void testModifyMultiValuedRdnVariant4() throws Exception
689     {
690         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
691         
692         Attributes attributes = createPerson( "description" );
693         String oldRdn = getRdn( attributes, "description" );
694         String newRdn = getRdn( attributes, "cn", "sn" );
695 
696         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
697         ctx.rename( oldRdn, newRdn );
698 
699         // Check whether new Entry exists
700         DirContext newCtx = ( DirContext ) ctx.lookup( newRdn );
701         assertNotNull( newCtx );
702 
703         // Check attributes
704         Attribute cnAttr = newCtx.getAttributes( "" ).get( "cn" );
705         assertEquals( 1, cnAttr.size() );
706         assertTrue( cnAttr.contains( "Tori Amos" ) );
707         Attribute snAttr = newCtx.getAttributes( "" ).get( "sn" );
708         assertEquals( 1, snAttr.size() );
709         assertTrue( snAttr.contains( "Amos" ) );
710         Attribute descriptionAttr = newCtx.getAttributes( "" ).get( "description" );
711         assertNull( descriptionAttr );
712 
713         // Remove entry (use new rdn)
714         ctx.unbind( newRdn );
715     }
716 
717 
718     /**
719      * Test for DIRSERVER-1162 and DIRSERVER-1085.
720      * 
721      * Modify single valued RDN to a multi valued RDN.
722      * - Old Rdn: cn
723      * - New Rdn: sn+telephoneNumber
724      * - Keep old Rdn
725      * - Attributes: cn, sn, description, telephoneNumber must exist 
726      * 
727      * @throws NamingException
728      */
729     @Test
730     @Ignore ( "Until this is fixed: https://issues.apache.org/jira/browse/DIRSERVER-1231" )
731     public void testModifyMultiValuedRdnVariant5() throws Exception
732     {
733         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
734         
735         Attributes attributes = createPerson( "cn" );
736         attributes.put( "telephoneNumber", "12345" );
737         String oldRdn = getRdn( attributes, "cn" );
738         String newRdn = getRdn( attributes, "sn", "telephoneNumber" );
739 
740         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
741         ctx.rename( oldRdn, newRdn );
742 
743         // Check whether new Entry exists
744         DirContext newCtx = ( DirContext ) ctx.lookup( newRdn );
745         assertNotNull( newCtx );
746 
747         // Check attributes
748         Attribute cnAttr = newCtx.getAttributes( "" ).get( "cn" );
749         assertEquals( 1, cnAttr.size() );
750         assertTrue( cnAttr.contains( "Tori Amos" ) );
751         Attribute snAttr = newCtx.getAttributes( "" ).get( "sn" );
752         assertEquals( 1, snAttr.size() );
753         assertTrue( snAttr.contains( "Amos" ) );
754         Attribute descriptionAttr = newCtx.getAttributes( "" ).get( "description" );
755         assertEquals( 1, descriptionAttr.size() );
756         Attribute telephoneNumberAttr = newCtx.getAttributes( "" ).get( "telephoneNumber" );
757         assertEquals( 1, telephoneNumberAttr.size() );
758         assertTrue( telephoneNumberAttr.contains( "12345" ) );
759 
760         // Remove entry (use new rdn)
761         ctx.unbind( newRdn );
762     }
763 
764 
765     /**
766      * Test for DIRSERVER-1162 and DIRSERVER-1085.
767      * 
768      * Modify single valued RDN to a multi valued RDN.
769      * - Old Rdn: cn
770      * - New Rdn: sn+telephoneNumber
771      * - Delete old Rdn
772      * - Must fail with schema violation, cn cannot be deleted
773      * 
774      * @throws NamingException
775      */
776     @Test
777     @Ignore ( "Until this is fixed: https://issues.apache.org/jira/browse/DIRSERVER-1231" )
778     public void testModifyMultiValuedRdnVariant6() throws Exception
779     {
780         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
781         
782         Attributes attributes = createPerson( "cn" );
783         attributes.put( "telephoneNumber", "12345" );
784         String oldRdn = getRdn( attributes, "cn" );
785         String newRdn = getRdn( attributes, "sn", "telephoneNumber" );
786 
787         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
788         try
789         {
790             ctx.rename( oldRdn, newRdn );
791             fail( "Rename must fail, cn can not be deleted from a person." );
792         }
793         catch ( SchemaViolationException ignored )
794         {
795             // expected behaviour
796         }
797 
798         // Check that entry was not changed
799         try
800         {
801             ctx.lookup( newRdn );
802             fail( "Previous rename failed as expected, entry must not exist" );
803         }
804         catch ( NameNotFoundException ignored )
805         {
806             // expected behaviour
807         }
808 
809         // Check that entry was not changed
810         DirContext oldCtx = ( DirContext ) ctx.lookup( oldRdn );
811         assertNotNull( oldCtx );
812         Attribute cnAttr = oldCtx.getAttributes( "" ).get( "cn" );
813         assertEquals( 1, cnAttr.size() );
814         assertTrue( cnAttr.contains( "Tori Amos" ) );
815         Attribute snAttr = oldCtx.getAttributes( "" ).get( "sn" );
816         assertEquals( 1, snAttr.size() );
817         assertTrue( snAttr.contains( "Amos" ) );
818         Attribute descriptionAttr = oldCtx.getAttributes( "" ).get( "description" );
819         assertEquals( 1, descriptionAttr.size() );
820 
821         // Remove entry (use old rdn)
822         ctx.unbind( oldRdn );
823     }
824 
825 
826     /**
827      * Test for DIRSERVER-1162 and DIRSERVER-1085.
828      * 
829      * Modify multi valued RDN to a single valued RDN.
830      * - Old Rdn: cn+sn
831      * - New Rdn: cn
832      * - Keep old Rdn
833      * - Attributes: cn, sn, description must exist 
834      * 
835      * @throws NamingException
836      */
837     @Test
838     public void testModifyMultiValuedRdnVariant7() throws Exception
839     {
840         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
841         
842         Attributes attributes = createPerson( "cn", "sn" );
843         String oldRdn = getRdn( attributes, "cn", "sn" );
844         String newRdn = getRdn( attributes, "cn" );
845 
846         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
847         ctx.rename( oldRdn, newRdn );
848 
849         // Check whether new Entry exists
850         DirContext newCtx = ( DirContext ) ctx.lookup( newRdn );
851         assertNotNull( newCtx );
852 
853         // Check attributes
854         Attribute cnAttr = newCtx.getAttributes( "" ).get( "cn" );
855         assertEquals( 1, cnAttr.size() );
856         assertTrue( cnAttr.contains( "Tori Amos" ) );
857         Attribute snAttr = newCtx.getAttributes( "" ).get( "sn" );
858         assertEquals( 1, snAttr.size() );
859         assertTrue( snAttr.contains( "Amos" ) );
860         Attribute descriptionAttr = newCtx.getAttributes( "" ).get( "description" );
861         assertEquals( 1, descriptionAttr.size() );
862 
863         // Remove entry (use new rdn)
864         ctx.unbind( newRdn );
865     }
866 
867 
868     /**
869      * Test for DIRSERVER-1162 and DIRSERVER-1085.
870      * 
871      * Modify multi valued RDN to a single valued RDN.
872      * - Old Rdn: cn+sn
873      * - New Rdn: cn
874      * - Delete old Rdn
875      * - Must fail with schema violation, cn cannot be deleted
876      * 
877      * @throws NamingException
878      */
879     @Test
880     public void testModifyMultiValuedRdnVariant8() throws Exception
881     {
882         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
883         
884         Attributes attributes = createPerson( "cn", "sn" );
885         String oldRdn = getRdn( attributes, "cn", "sn" );
886         String newRdn = getRdn( attributes, "cn" );
887 
888         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
889         try
890         {
891             ctx.rename( oldRdn, newRdn );
892             fail( "Rename must fail, cn can not be deleted from a person." );
893         }
894         catch ( SchemaViolationException ignored )
895         {
896             // expected behaviour
897         }
898 
899         // Check that entry was not changed
900         try
901         {
902             ctx.lookup( newRdn );
903             fail( "Previous rename failed as expected, entry must not exist" );
904         }
905         catch ( NameNotFoundException ignored )
906         {
907             // expected behaviour
908         }
909 
910         // Check that entry was not changed
911         DirContext oldCtx = ( DirContext ) ctx.lookup( oldRdn );
912         assertNotNull( oldCtx );
913         Attribute cnAttr = oldCtx.getAttributes( "" ).get( "cn" );
914         assertEquals( 1, cnAttr.size() );
915         assertTrue( cnAttr.contains( "Tori Amos" ) );
916         Attribute snAttr = oldCtx.getAttributes( "" ).get( "sn" );
917         assertEquals( 1, snAttr.size() );
918         assertTrue( snAttr.contains( "Amos" ) );
919         Attribute descriptionAttr = oldCtx.getAttributes( "" ).get( "description" );
920         assertEquals( 1, descriptionAttr.size() );
921 
922         // Remove entry (use old rdn)
923         ctx.unbind( oldRdn );
924     }
925 
926 
927     /**
928      * Test for DIRSERVER-1162 and DIRSERVER-1085.
929      * 
930      * Tries to rename+deleteOldRdn an entry that has an operational attribute
931      * in its RDN. Must fail because an operational attribute can not be
932      * deleted.
933      * 
934      * @throws NamingException
935      */
936     @Test
937     public void testModifyRdnOperationalAttribute() throws Exception
938     {
939         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
940         
941         // create the entry
942         Attributes attributes = createPerson( "cn" );
943         String oldRdn = getRdn( attributes, "cn" );
944 
945         // read createTimestamp
946         String createTimestamp = ( String ) ctx.getAttributes( oldRdn, new String[]
947             { "createTimestamp" } ).get( "createTimestamp" ).get();
948 
949         // rename to createTimstamp=YYYYMMDDHHMMSSZ
950         String newRdn = "createTimestamp=" + createTimestamp;
951         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
952         ctx.rename( oldRdn, newRdn );
953 
954         // rename back to old Rdn, enable deleteOldRdn, 
955         // must fail with NoPermisionException
956         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
957         try
958         {
959             ctx.rename( newRdn, oldRdn );
960             fail( "Rename must fail, operational attribute createTimestamp can not be deleted." );
961         }
962         catch ( NoPermissionException ignored )
963         {
964             // expected behaviour
965         }
966 
967         // Remove entry (use new rdn)
968         ctx.unbind( newRdn );
969     }
970 
971 
972     /**
973      * Test for DIRSERVER-1162 and DIRSERVER-1085.
974      * 
975      * Tries to rename+deleteOldRdn an entry that has the structural object class
976      * person in its RDN (objectClass=person,ou=system). Must fail because the 
977      * structural object class can not be deleted.
978      * 
979      * @throws NamingException
980      */
981     @Test
982     public void testModifyRdnObjectClassAttribute() throws Exception
983     {
984         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
985         
986         // create the entry
987         Attributes attributes = createPerson( "cn" );
988         String oldRdn = getRdn( attributes, "cn" );
989 
990         // rename to objectClass=person
991         String newRdn = "objectClass=person";
992         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "false" );
993         ctx.rename( oldRdn, newRdn );
994 
995         // rename back to old Rdn, enable deleteOldRdn, 
996         // must fail with NoPermisionException
997         ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
998         try
999         {
1000             ctx.rename( newRdn, oldRdn );
1001             fail( "Rename must fail, structural objectClass person can not be deleted." );
1002         }
1003         catch ( SchemaViolationException ignored )
1004         {
1005             // expected behaviour
1006         }
1007 
1008         // Remove entry (use new rdn)
1009         ctx.unbind( newRdn );
1010     }
1011 
1012 
1013     private String getRdn( Attributes attributes, String... rdnTypes ) throws Exception
1014     {
1015         String rdn = "";
1016         
1017         for ( String type : rdnTypes )
1018         {
1019             rdn += type + "=" + attributes.get( type ).get() + "+";
1020         }
1021 
1022         rdn = rdn.substring( 0, rdn.length() - 1 );
1023         return rdn;
1024     }
1025 
1026 
1027     private Attributes createPerson( String... rdnTypes ) throws Exception
1028     {
1029         DirContext ctx = ( DirContext ) getWiredContext( ldapService ).lookup( BASE );
1030         
1031         Attributes attributes = new BasicAttributes( true );
1032         Attribute attribute = new BasicAttribute( "objectClass" );
1033         attribute.add( "top" );
1034         attribute.add( "person" );
1035         attributes.put( attribute );
1036         attributes.put( "cn", "Tori Amos" );
1037         attributes.put( "sn", "Amos" );
1038         attributes.put( "description", "Tori Amos is a person." );
1039 
1040         String rdn = getRdn( attributes, rdnTypes );
1041 
1042         ctx.createSubcontext( rdn, attributes );
1043 
1044         return attributes;
1045     }
1046 }