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.core.operational;
21  
22  
23  import org.apache.directory.server.core.DirectoryService;
24  import org.apache.directory.server.core.entry.DefaultServerEntry;
25  import org.apache.directory.server.core.integ.CiRunner;
26  import static org.apache.directory.server.core.integ.IntegrationUtils.getSystemContext;
27  import static org.apache.directory.server.core.integ.IntegrationUtils.getUserAddLdif;
28  import org.apache.directory.shared.ldap.constants.JndiPropertyConstants;
29  import org.apache.directory.shared.ldap.ldif.LdifEntry;
30  import org.apache.directory.shared.ldap.message.AliasDerefMode;
31  import org.apache.directory.shared.ldap.util.StringTools;
32  import static org.junit.Assert.assertEquals;
33  import static org.junit.Assert.assertTrue;
34  import static org.junit.Assert.assertFalse;
35  import static org.junit.Assert.assertNull;
36  import static org.junit.Assert.assertNotNull;
37  import static org.junit.Assert.fail;
38  import org.junit.Test;
39  import org.junit.runner.RunWith;
40  
41  import javax.naming.NamingEnumeration;
42  import javax.naming.NamingException;
43  import javax.naming.NoPermissionException;
44  import javax.naming.directory.Attribute;
45  import javax.naming.directory.Attributes;
46  import javax.naming.directory.BasicAttribute;
47  import javax.naming.directory.BasicAttributes;
48  import javax.naming.directory.DirContext;
49  import javax.naming.directory.InvalidAttributeValueException;
50  import javax.naming.directory.ModificationItem;
51  import javax.naming.directory.SearchControls;
52  import javax.naming.directory.SearchResult;
53  import javax.naming.ldap.LdapContext;
54  
55  
56  /**
57   * Tests the methods on JNDI contexts that are analogous to entry modify
58   * operations in LDAP.
59   *
60   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
61   * @version $Rev: 691179 $
62   */
63  @RunWith ( CiRunner.class )
64  public class OperationalAttributeServiceIT
65  {
66      private static final String BINARY_KEY = "java.naming.ldap.attributes.binary";
67      private static final String RDN_KATE_BUSH = "cn=Kate Bush";
68  
69  
70      public static DirectoryService service;
71  
72  
73      protected Attributes getPersonAttributes( String sn, String cn )
74      {
75          Attributes attrs = new BasicAttributes( true );
76          Attribute ocls = new BasicAttribute( "objectClass" );
77          ocls.add( "top" );
78          ocls.add( "person" );
79          attrs.put( ocls );
80          attrs.put( "cn", cn );
81          attrs.put( "sn", sn );
82  
83          return attrs;
84      }
85  
86  
87      /**
88       * @todo add this to an LDIF annotation
89       *
90       * @param sysRoot the system root context at ou=system as the admin
91       * @throws NamingException on error
92       */
93      protected void createData( LdapContext sysRoot ) throws NamingException
94      {
95          // Create an entry for Kate Bush
96          Attributes attrs = getPersonAttributes( "Bush", "Kate Bush" );
97          DirContext ctx = sysRoot.createSubcontext( RDN_KATE_BUSH, attrs );
98          assertNotNull( ctx );
99      }
100 
101 
102     @Test
103     public void testBinaryAttributeFilterExtension() throws Exception
104     {
105         LdapContext sysRoot = getSystemContext( service );
106         createData( sysRoot );
107 
108         Attributes attributes = new BasicAttributes( true );
109         Attribute oc = new BasicAttribute( "objectClass", "top" );
110         oc.add( "person" );
111         oc.add( "organizationalPerson" );
112         oc.add( "inetOrgPerson" );
113         attributes.put( oc );
114 
115         attributes.put( "ou", "test" );
116         attributes.put( "cn", "test" );
117         attributes.put( "sn", "test" );
118 
119         sysRoot.createSubcontext( "ou=test", attributes );
120 
121         // test without turning on the property
122         DirContext ctx = ( DirContext ) sysRoot.lookup( "ou=test" );
123         Attribute ou = ctx.getAttributes( "" ).get( "ou" );
124         Object value = ou.get();
125         assertTrue( value instanceof String );
126 
127         // test with the property now making ou into a binary value
128         sysRoot.addToEnvironment( BINARY_KEY, "ou" );
129         ctx = ( DirContext ) sysRoot.lookup( "ou=test" );
130         ou = ctx.getAttributes( "" ).get( "ou" );
131         value = ou.get();
132         assertEquals( "test", value );
133 
134         // try jpegPhoto which should be binary automatically - use ou as control
135         byte[] keyValue = new byte[]
136             { (byte)0xFF, (byte)0xD8, (byte)0xFF, (byte)0xE0, 0x01, 0x02, 'J', 'F', 'I', 'F', 0x00, 0x45, 0x23, 0x7d, 0x7f };
137         attributes.put( "jpegPhoto", keyValue );
138         sysRoot.createSubcontext( "ou=anothertest", attributes );
139         ctx = ( DirContext ) sysRoot.lookup( "ou=anothertest" );
140         ou = ctx.getAttributes( "" ).get( "ou" );
141         value = ou.get();
142         assertEquals( "anothertest", value );
143         Attribute jpegPhoto = ctx.getAttributes( "" ).get( "jpegPhoto" );
144         value = jpegPhoto.get();
145         assertTrue( value instanceof byte[] );
146         assertEquals( "0xFF 0xD8 0xFF 0xE0 0x01 0x02 0x4A 0x46 0x49 0x46 0x00 0x45 0x23 0x7D 0x7F ", StringTools.dumpBytes( ( byte[] ) value ) );
147 
148         // try jpegPhoto which should be binary automatically but use String to
149         // create so we should still get back a byte[] - use ou as control
150         /*attributes.remove( "jpegPhoto" );
151         attributes.put( "jpegPhoto", "testing a string" );
152         sysRoot.createSubcontext( "ou=yetanothertest", attributes );
153         ctx = ( DirContext ) sysRoot.lookup( "ou=yetanothertest" );
154         ou = ctx.getObject( "" ).get( "ou" );
155         value = ou.get();
156         assertEquals( "yetanothertest", value );
157         jpegPhoto = ctx.getObject( "" ).get( "jpegPhoto" );
158         value = jpegPhoto.get();
159         assertTrue( value instanceof byte[] );*/
160     }
161 
162 
163     @Test
164     public void testModifyOperationalOpAttrs() throws Exception
165     {
166         LdapContext sysRoot = getSystemContext( service );
167         createData( sysRoot );
168 
169         /*
170          * create ou=testing00,ou=system
171          */
172         Attributes attributes = new BasicAttributes( true );
173         Attribute attribute = new BasicAttribute( "objectClass" );
174         attribute.add( "top" );
175         attribute.add( "organizationalUnit" );
176         attributes.put( attribute );
177         attributes.put( "ou", "testing00" );
178         DirContext ctx = sysRoot.createSubcontext( "ou=testing00", attributes );
179         assertNotNull( ctx );
180 
181         ctx = ( DirContext ) sysRoot.lookup( "ou=testing00" );
182         assertNotNull( ctx );
183 
184         attributes = ctx.getAttributes( "" );
185         assertNotNull( attributes );
186         assertEquals( "testing00", attributes.get( "ou" ).get() );
187         attribute = attributes.get( "objectClass" );
188         assertNotNull( attribute );
189         assertTrue( attribute.contains( "top" ) );
190         assertTrue( attribute.contains( "organizationalUnit" ) );
191         assertNull( attributes.get( "createTimestamp" ) );
192         assertNull( attributes.get( "creatorsName" ) );
193 
194         SearchControls ctls = new SearchControls();
195         ctls.setReturningAttributes( new String[]
196             { "ou", "createTimestamp", "creatorsName" } );
197 
198         sysRoot.addToEnvironment( JndiPropertyConstants.JNDI_LDAP_DAP_DEREF_ALIASES,
199                 AliasDerefMode.NEVER_DEREF_ALIASES.getJndiValue() );
200         NamingEnumeration<SearchResult> list;
201         list = sysRoot.search( "", "(ou=testing00)", ctls );
202         SearchResult result = list.next();
203         list.close();
204 
205         assertNotNull( result.getAttributes().get( "ou" ) );
206         assertNotNull( result.getAttributes().get( "creatorsName" ) );
207         assertNotNull( result.getAttributes().get( "createTimestamp" ) );
208     }
209 
210 
211     /**
212      * Checks to confirm that the system context root ou=system has the
213      * required operational attributes.  Since this is created automatically
214      * on system database creation properties the create attributes must be
215      * specified.  There are no interceptors in effect when this happens so
216      * we must test explicitly.
217      *
218      * @see <a href="http://nagoya.apache.org/jira/browse/DIREVE-57">DIREVE-57:
219      * ou=system does not contain operational attributes</a>
220      *
221      * @throws NamingException on error
222      */
223     @Test
224     public void testSystemContextRoot() throws Exception
225     {
226         LdapContext sysRoot = getSystemContext( service );
227         createData( sysRoot );
228 
229         SearchControls controls = new SearchControls();
230         controls.setSearchScope( SearchControls.OBJECT_SCOPE );
231         
232         NamingEnumeration<SearchResult> list;
233         list = sysRoot.search( "", "(objectClass=*)", controls );
234         SearchResult result = list.next();
235 
236         // test to make sure op attribute do not occur - this is the control
237         Attributes attributes = result.getAttributes();
238         assertNull( attributes.get( "creatorsName" ) );
239         assertNull( attributes.get( "createTimestamp" ) );
240 
241         // now we ask for all the op attributes and check to get them
242         String[] ids = new String[]
243             { "creatorsName", "createTimestamp" };
244         controls.setReturningAttributes( ids );
245         list = sysRoot.search( "", "(objectClass=*)", controls );
246         result = list.next();
247         attributes = result.getAttributes();
248         assertNotNull( attributes.get( "creatorsName" ) );
249         assertNotNull( attributes.get( "createTimestamp" ) );
250     }
251 
252 
253     /**
254      * Test which confirms that all new users created under the user's dn
255      * (ou=users,ou=system) have the creatorsName set to the DN of the new
256      * user even though the admin is creating the user.  This is the basis
257      * for some authorization rules to protect passwords.
258      *
259      * NOTE THIS CHANGE WAS REVERTED SO WE ADAPTED THE TEST TO MAKE SURE THE
260      * CHANGE DOES NOT PERSIST!
261      *
262      * @see <a href="http://nagoya.apache.org/jira/browse/DIREVE-67">JIRA Issue DIREVE-67</a>
263      *
264      * @throws NamingException on error
265      */
266     @Test
267     public void testConfirmNonAdminUserDnIsCreatorsName() throws Exception
268     {
269         LdifEntry akarasulu = getUserAddLdif();
270         service.getAdminSession().add( 
271             new DefaultServerEntry( service.getRegistries(), akarasulu.getEntry() ) ); 
272 
273         LdapContext sysRoot = getSystemContext( service );
274         createData( sysRoot );
275 
276         Attributes attributes = sysRoot.getAttributes( "uid=akarasulu,ou=users", new String[]
277             { "creatorsName" } );
278 
279         assertFalse( "uid=akarasulu,ou=users,ou=system".equals( attributes.get( "creatorsName" ).get() ) );
280     }
281 
282     
283     /**
284      * Modify an entry and check whether attributes modifiersName and modifyTimestamp are present.
285      *
286      * @throws NamingException on error
287      */
288     @Test
289     public void testModifyShouldLeadToModifiersAttributes() throws Exception
290     {
291         LdapContext sysRoot = getSystemContext( service );
292         createData( sysRoot );
293 
294         ModificationItem modifyOp = new ModificationItem( DirContext.ADD_ATTRIBUTE, new BasicAttribute( "description",
295             "Singer Songwriter" ) );
296 
297         sysRoot.modifyAttributes( RDN_KATE_BUSH, new ModificationItem[]
298             { modifyOp } );
299 
300         SearchControls controls = new SearchControls();
301         controls.setSearchScope( SearchControls.OBJECT_SCOPE );
302         String[] ids = new String[]
303             { "modifiersName", "modifyTimestamp" };
304         controls.setReturningAttributes( ids );
305 
306         NamingEnumeration<SearchResult> list = sysRoot.search( RDN_KATE_BUSH, "(objectClass=*)", controls );
307         SearchResult result = list.next();
308         Attributes attributes = result.getAttributes();
309         assertNotNull( attributes.get( "modifiersName" ) );
310         assertNotNull( attributes.get( "modifyTimestamp" ) );
311     }
312     
313     
314     /**
315      * Modify an entry and check whether attribute modifyTimestamp changes.
316      *
317      * @throws NamingException on error
318      * @throws InterruptedException on error
319      */
320     @Test
321     public void testModifyShouldChangeModifyTimestamp() throws Exception, InterruptedException
322     {
323         LdapContext sysRoot = getSystemContext( service );
324         createData( sysRoot );
325 
326         // Add attribute description to entry
327         ModificationItem modifyAddOp = new ModificationItem( DirContext.ADD_ATTRIBUTE, new BasicAttribute(
328             "description", "an English singer, songwriter, musician" ) );
329         sysRoot.modifyAttributes( RDN_KATE_BUSH, new ModificationItem[]
330             { modifyAddOp } );
331 
332         // Determine modifyTimestamp
333         SearchControls controls = new SearchControls();
334         controls.setSearchScope( SearchControls.OBJECT_SCOPE );
335         String[] ids = new String[]
336             { "modifyTimestamp" };
337         controls.setReturningAttributes( ids );
338         NamingEnumeration<SearchResult> list = sysRoot.search( RDN_KATE_BUSH, "(objectClass=*)", controls );
339         SearchResult result = list.next();
340         Attributes attributes = result.getAttributes();
341         Attribute modifyTimestamp = attributes.get( "modifyTimestamp" );
342         assertNotNull( modifyTimestamp );
343         String oldTimestamp = modifyTimestamp.get().toString();
344         
345         // Wait two seconds
346         Thread.sleep( 2000 );
347 
348         // Change value of attribute description
349         ModificationItem modifyOp = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(
350             "description", "one of England's most successful solo female performers" ) );
351         sysRoot.modifyAttributes( RDN_KATE_BUSH, new ModificationItem[]
352             { modifyOp } );
353 
354         // Determine modifyTimestamp after modification
355         list = sysRoot.search( RDN_KATE_BUSH, "(objectClass=*)", controls );
356         result = list.next();
357         attributes = result.getAttributes();
358         modifyTimestamp = attributes.get( "modifyTimestamp" );
359         assertNotNull( modifyTimestamp );
360         String newTimestamp = modifyTimestamp.get().toString();
361         
362         // assert the value has changed
363         assertFalse( oldTimestamp.equals( newTimestamp ) );
364     }
365 
366 
367     /**
368      * Try to add modifiersName attribute to an entry
369      *
370      * @throws NamingException on error
371      */
372     @Test
373     public void testModifyOperationalAttributeAdd() throws Exception
374     {
375         LdapContext sysRoot = getSystemContext( service );
376         createData( sysRoot );
377 
378         ModificationItem modifyOp = new ModificationItem( DirContext.ADD_ATTRIBUTE, new BasicAttribute(
379             "modifiersName", "cn=Tori Amos,dc=example,dc=com" ) );
380 
381         try
382         {
383             sysRoot.modifyAttributes( RDN_KATE_BUSH, new ModificationItem[]
384                 { modifyOp } );
385             fail( "modification of entry should fail" );
386         }
387         catch ( InvalidAttributeValueException e )
388         {
389             // expected
390         }
391         catch ( NoPermissionException e )
392         {
393             // expected
394         }
395     }
396 
397 
398     /**
399      * Try to remove creatorsName attribute from an entry.
400      *
401      * @throws NamingException on error
402      */
403     @Test
404     public void testModifyOperationalAttributeRemove() throws Exception
405     {
406         LdapContext sysRoot = getSystemContext( service );
407         createData( sysRoot );
408 
409         ModificationItem modifyOp = new ModificationItem( DirContext.REMOVE_ATTRIBUTE, new BasicAttribute(
410             "creatorsName" ) );
411 
412         try
413         {
414             sysRoot.modifyAttributes( RDN_KATE_BUSH, new ModificationItem[]
415                 { modifyOp } );
416             fail( "modification of entry should fail" );
417         }
418         catch ( InvalidAttributeValueException e )
419         {
420             // expected
421         }
422         catch ( NoPermissionException e )
423         {
424             // expected
425         }
426     }
427 
428 
429     /**
430      * Try to replace creatorsName attribute on an entry.
431      *
432      * @throws NamingException on error
433      */
434     @Test
435     public void testModifyOperationalAttributeReplace() throws Exception
436     {
437         LdapContext sysRoot = getSystemContext( service );
438         createData( sysRoot );
439 
440         ModificationItem modifyOp = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(
441             "creatorsName", "cn=Tori Amos,dc=example,dc=com" ) );
442 
443         try
444         {
445             sysRoot.modifyAttributes( RDN_KATE_BUSH, new ModificationItem[]
446                 { modifyOp } );
447             fail( "modification of entry should fail" );
448         }
449         catch ( InvalidAttributeValueException e )
450         {
451             // expected
452         }
453         catch ( NoPermissionException e )
454         {
455             // expected
456         }
457     }
458 }