1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package org.apache.ldap.server.authz;
18  
19  
20  import org.apache.ldap.common.exception.LdapNoPermissionException;
21  import org.apache.ldap.common.name.LdapName;
22  
23  import javax.naming.NamingException;
24  import javax.naming.NamingEnumeration;
25  import javax.naming.Name;
26  import javax.naming.directory.*;
27  import java.util.List;
28  import java.util.ArrayList;
29  
30  
31  /***
32   * Tests whether or not authorization around entry modify operations work properly.
33   *
34   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
35   * @version $Rev$
36   */
37  public class ModifyAuthorizationTest extends AbstractAuthorizationTest
38  {
39      /***
40       * Checks if an attribute of a simple entry (an organizationalUnit) with an RDN
41       * relative to ou=system can be modified by a specific non-admin user.  If a
42       * permission exception is encountered it is caught and false is returned,
43       * otherwise true is returned.  The entry is deleted after being created just in case
44       * subsequent calls to this method are made in the same test case: the admin account
45       * is used to add and delete this test entry so permissions to add and delete are not
46       * required to test the modify operation by the user.
47       *
48       * @param uid the unique identifier for the user (presumed to exist under ou=users,ou=system)
49       * @param password the password of this user
50       * @param entryRdn the relative DN, relative to ou=system where entry is created
51       * for modification test
52       * @param mods the modifications to make to the entry
53       * @return true if the modifications can be made by the user at the specified location,
54       * false otherwise.
55       * @throws javax.naming.NamingException if there are problems conducting the test
56       */
57      public boolean checkCanModifyAs( String uid, String password, String entryRdn, ModificationItem[] mods )
58              throws NamingException
59      {
60          // create the entry with the telephoneNumber attribute to modify
61          Attributes testEntry = new BasicAttributes( "ou", "testou", true );
62          Attribute objectClass = new BasicAttribute( "objectClass" );
63          testEntry.put( objectClass );
64          objectClass.add( "top" );
65          objectClass.add( "organizationalUnit" );
66          testEntry.put( "telephoneNumber", "867-5309" );  // jenny don't change your number
67  
68          DirContext adminContext = getContextAsAdmin();
69  
70          try
71          {
72              // create the entry as admin
73              LdapName userName = new LdapName( "uid="+uid+",ou=users,ou=system" );
74              adminContext.createSubcontext( entryRdn, testEntry );
75  
76              // modify the entry as the user
77              DirContext userContext = getContextAs( userName, password );
78              userContext.modifyAttributes( entryRdn, mods );
79  
80              return true;
81          }
82          catch ( LdapNoPermissionException e )
83          {
84              return false;
85          }
86          finally
87          {
88              // let's clean up
89              adminContext.destroySubcontext( entryRdn );
90          }
91      }
92  
93  
94      /***
95       * Checks if an attribute of a simple entry (an organizationalUnit) with an RDN
96       * relative to ou=system can be modified by a specific non-admin user.  If a
97       * permission exception is encountered it is caught and false is returned,
98       * otherwise true is returned.  The entry is deleted after being created just in case
99       * subsequent calls to this method are made in the same test case: the admin account
100      * is used to add and delete this test entry so permissions to add and delete are not
101      * required to test the modify operation by the user.
102      *
103      * @param uid the unique identifier for the user (presumed to exist under ou=users,ou=system)
104      * @param password the password of this user
105      * @param entryRdn the relative DN, relative to ou=system where entry is created
106      * for modification test
107      * @param mods the attributes to modify in the entry
108      * @param modOp the modification operation to use for all attributes
109      * @return true if the modifications can be made by the user at the specified location,
110      * false otherwise.
111      * @throws javax.naming.NamingException if there are problems conducting the test
112      */
113     public boolean checkCanModifyAs( String uid, String password, String entryRdn, int modOp, Attributes mods )
114             throws NamingException
115     {
116         // create the entry with the telephoneNumber attribute to modify
117         Attributes testEntry = new BasicAttributes( "ou", "testou", true );
118         Attribute objectClass = new BasicAttribute( "objectClass" );
119         testEntry.put( objectClass );
120         objectClass.add( "top" );
121         objectClass.add( "organizationalUnit" );
122         testEntry.put( "telephoneNumber", "867-5309" );  // jenny don't change your number
123 
124         DirContext adminContext = getContextAsAdmin();
125 
126         try
127         {
128             // create the entry as admin
129             LdapName userName = new LdapName( "uid="+uid+",ou=users,ou=system" );
130             adminContext.createSubcontext( entryRdn, testEntry );
131 
132             // modify the entry as the user
133             DirContext userContext = getContextAs( userName, password );
134             userContext.modifyAttributes( entryRdn, modOp, mods );
135 
136             return true;
137         }
138         catch ( LdapNoPermissionException e )
139         {
140             return false;
141         }
142         finally
143         {
144             // let's clean up
145             adminContext.destroySubcontext( entryRdn );
146         }
147     }
148 
149 
150     /***
151      * Checks if a user can modify an attribute of their own entry.  Users are
152      * presumed to reside under ou=users,ou=system.  If a permission exception is
153      * encountered it is caught and false is returned, otherwise true is returned.
154      *
155      * @param uid the unique identifier for the user (presumed to exist under ou=users,ou=system)
156      * @param password the password of this user
157      * @param mods the attributes to modify in the entry
158      * @param modOp the modification operation to use for all attributes
159      * @return true if the modifications can be made by the user his/her own entry,
160      * false otherwise.
161      * @throws javax.naming.NamingException if there are problems conducting the test
162      */
163     public boolean checkCanSelfModify( String uid, String password, int modOp, Attributes mods )
164             throws NamingException
165     {
166         try
167         {
168             // modify the entry as the user
169             Name userEntry = new LdapName( "uid="+uid+",ou=users,ou=system" );
170             DirContext userContext = getContextAs( userEntry, password, userEntry.toString() );
171             userContext.modifyAttributes( "", modOp, mods );
172             return true;
173         }
174         catch ( LdapNoPermissionException e )
175         {
176             return false;
177         }
178     }
179 
180 
181     /***
182      * Checks if a user can modify an attribute of their own entry.  Users are
183      * presumed to reside under ou=users,ou=system.  If a permission exception is
184      * encountered it is caught and false is returned, otherwise true is returned.
185      *
186      * @param uid the unique identifier for the user (presumed to exist under ou=users,ou=system)
187      * @param password the password of this user
188      * @param mods the attributes to modify in the entry
189      * @return true if the modifications can be made by the user his/her own entry,
190      * false otherwise.
191      * @throws javax.naming.NamingException if there are problems conducting the test
192      */
193     public boolean checkCanSelfModify( String uid, String password, ModificationItem[] mods )
194             throws NamingException
195     {
196         try
197         {
198             // modify the entry as the user
199             Name userEntry = new LdapName( "uid="+uid+",ou=users,ou=system" );
200             DirContext userContext = getContextAs( userEntry, password, userEntry.toString() );
201             userContext.modifyAttributes( "", mods );
202             return true;
203         }
204         catch ( LdapNoPermissionException e )
205         {
206             return false;
207         }
208     }
209 
210 
211     /***
212      * Converts a set of attributes and a modification operation type into a MoficationItem array.
213      *
214      * @param modOp the modification operation to perform
215      * @param changes the modifications to the attribute
216      * @return the array of modification items represting the changes
217      * @throws NamingException if there are problems accessing attributes
218      */
219     private ModificationItem[] toItems( int modOp, Attributes changes ) throws NamingException
220     {
221         List mods = new ArrayList();
222         NamingEnumeration list = changes.getAll();
223         while ( list.hasMore() )
224         {
225             Attribute attr = ( Attribute ) list.next();
226             mods.add( new ModificationItem( modOp, attr ) );
227         }
228         ModificationItem[] modArray = new ModificationItem[mods.size()];
229         return ( ModificationItem[] ) mods.toArray( modArray );
230     }
231 
232 
233     public void testSelfModification() throws NamingException
234     {
235         // ----------------------------------------------------------------------------------
236         // Modify with Attribute Addition
237         // ----------------------------------------------------------------------------------
238 
239         // create the non-admin user
240         createUser( "billyd", "billyd" );
241 
242         // create the password modification
243         ModificationItem[] mods = toItems( DirContext.REPLACE_ATTRIBUTE,
244                 new BasicAttributes( "userPassword", "williams", true ) );
245 
246         // try a modify operation which should fail without any ACI
247         assertFalse( checkCanSelfModify( "billyd", "billyd", mods ) );
248 
249         // Gives grantModify, and grantRead perm to all users in the Administrators group for
250         // entries and all attribute types and values
251         createAccessControlSubentry( "selfModifyUserPassword",
252                 "{ " +
253                 "identificationTag \"addAci\", " +
254                 "precedence 14, " +
255                 "authenticationLevel none, " +
256                 "itemOrUserFirst userFirst: { " +
257                 "userClasses { thisEntry }, " +
258                 "userPermissions { " +
259                         "{ protectedItems {entry}, grantsAndDenials { grantModify, grantBrowse, grantRead } }, " +
260                         "{ protectedItems {allAttributeValues {userPassword}}, grantsAndDenials { grantAdd, grantRemove } } " +
261                         "} } }" );
262 
263         // try a modify operation which should succeed with ACI
264         assertTrue( checkCanSelfModify( "billyd", "billyd", mods ) );
265         deleteAccessControlSubentry( "selfModifyUserPassword" );
266     }
267 
268 
269     /***
270      * Checks to make sure group membership based userClass works for modify operations.
271      *
272      * @throws javax.naming.NamingException if the test encounters an error
273      */
274     public void testGrantModifyByAdministrators() throws NamingException
275     {
276         // ----------------------------------------------------------------------------------
277         // Modify with Attribute Addition
278         // ----------------------------------------------------------------------------------
279 
280         // create the add modifications
281         ModificationItem[] mods = toItems( DirContext.ADD_ATTRIBUTE,
282                 new BasicAttributes( "registeredAddress", "100 Park Ave.", true ) );
283 
284         // create the non-admin user
285         createUser( "billyd", "billyd" );
286 
287         // try a modify operation which should fail without any ACI
288         assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", mods ) );
289 
290         // Gives grantModify, and grantRead perm to all users in the Administrators group for
291         // entries and all attribute types and values
292         createAccessControlSubentry( "administratorModifyAdd",
293                 "{ " +
294                 "identificationTag \"addAci\", " +
295                 "precedence 14, " +
296                 "authenticationLevel none, " +
297                 "itemOrUserFirst userFirst: { " +
298                 "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
299                 "userPermissions { " +
300                         "{ protectedItems {entry}, grantsAndDenials { grantModify, grantBrowse } }, " +
301                         "{ protectedItems {allAttributeValues {registeredAddress}}, grantsAndDenials { grantAdd } } " +
302                         "} } }" );
303 
304         // see if we can now add that test entry which we could not before
305         // add op should still fail since billd is not in the admin group
306         assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", mods ) );
307 
308         // now add billyd to the Administrator group and try again
309         addUserToGroup( "billyd", "Administrators" );
310 
311         // try a modify operation which should succeed with ACI and group membership change
312         assertTrue( checkCanModifyAs( "billyd", "billyd", "ou=testou", mods ) );
313         deleteAccessControlSubentry( "administratorModifyAdd" );
314 
315         // ----------------------------------------------------------------------------------
316         // Modify with Attribute Removal
317         // ----------------------------------------------------------------------------------
318 
319         // now let's test to see if we can perform a modify with a delete op
320         mods = toItems( DirContext.REMOVE_ATTRIBUTE,
321                 new BasicAttributes( "telephoneNumber", "867-5309", true ) );
322 
323         // make sure we cannot remove the telephone number from the test entry
324         assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", mods ) );
325 
326         // Gives grantModify, and grantRead perm to all users in the Administrators group for
327         // entries and all attribute types and values
328         createAccessControlSubentry( "administratorModifyRemove", "{ " +
329                 "identificationTag \"addAci\", " +
330                 "precedence 14, " +
331                 "authenticationLevel none, " +
332                 "itemOrUserFirst userFirst: { " +
333                 "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
334                 "userPermissions { " +
335                         "{ protectedItems {entry}, grantsAndDenials { grantModify, grantBrowse } }, " +
336                         "{ protectedItems {allAttributeValues {telephoneNumber}}, grantsAndDenials { grantRemove } } " +
337                         "} } }" );
338 
339         // try a modify operation which should succeed with ACI and group membership change
340         assertTrue( checkCanModifyAs( "billyd", "billyd", "ou=testou", mods ) );
341         deleteAccessControlSubentry( "administratorModifyRemove" );
342 
343         // ----------------------------------------------------------------------------------
344         // Modify with Attribute Replace (requires both grantRemove and grantAdd on attrs)
345         // ----------------------------------------------------------------------------------
346 
347         // now let's test to see if we can perform a modify with a delete op
348         mods = toItems( DirContext.REPLACE_ATTRIBUTE,
349                 new BasicAttributes( "telephoneNumber", "867-5309", true ) );
350 
351         // make sure we cannot remove the telephone number from the test entry
352         assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", mods ) );
353 
354         // Gives grantModify, and grantRead perm to all users in the Administrators group for
355         // entries and all attribute types and values
356         createAccessControlSubentry( "administratorModifyReplace", "{ " +
357                 "identificationTag \"addAci\", " +
358                 "precedence 14, " +
359                 "authenticationLevel none, " +
360                 "itemOrUserFirst userFirst: { " +
361                 "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
362                 "userPermissions { " +
363                         "{ protectedItems {entry}, grantsAndDenials { grantModify, grantBrowse } }, " +
364                         "{ protectedItems {allAttributeValues {telephoneNumber}}, grantsAndDenials { grantAdd, grantRemove } } " +
365                         "} } }" );
366 
367         // try a modify operation which should succeed with ACI and group membership change
368         assertTrue( checkCanModifyAs( "billyd", "billyd", "ou=testou", mods ) );
369         deleteAccessControlSubentry( "administratorModifyReplace" );
370 
371         /* =================================================================================
372          *              DO IT ALL OVER AGAIN BUT USE THE OTHER MODIFY METHOD
373          * ================================================================================= */
374 
375         // ----------------------------------------------------------------------------------
376         // Modify with Attribute Addition
377         // ----------------------------------------------------------------------------------
378 
379         // create the add modifications
380         Attributes changes = new BasicAttributes( "registeredAddress", "100 Park Ave.", true );
381 
382         // try a modify operation which should fail without any ACI
383         assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", DirContext.ADD_ATTRIBUTE, changes ) );
384 
385         // Gives grantModify, and grantRead perm to all users in the Administrators group for
386         // entries and all attribute types and values
387         createAccessControlSubentry( "administratorModifyAdd", "{ " +
388                 "identificationTag \"addAci\", " +
389                 "precedence 14, " +
390                 "authenticationLevel none, " +
391                 "itemOrUserFirst userFirst: { " +
392                 "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
393                 "userPermissions { " +
394                         "{ protectedItems {entry}, grantsAndDenials { grantModify, grantBrowse } }, " +
395                         "{ protectedItems {allAttributeValues {registeredAddress}}, grantsAndDenials { grantAdd } } " +
396                         "} } }" );
397 
398         // try a modify operation which should succeed with ACI and group membership change
399         assertTrue( checkCanModifyAs( "billyd", "billyd", "ou=testou", DirContext.ADD_ATTRIBUTE, changes ) );
400         deleteAccessControlSubentry( "administratorModifyAdd" );
401 
402         // ----------------------------------------------------------------------------------
403         // Modify with Attribute Removal
404         // ----------------------------------------------------------------------------------
405 
406         // now let's test to see if we can perform a modify with a delete op
407         changes = new BasicAttributes( "telephoneNumber", "867-5309", true );
408 
409         // make sure we cannot remove the telephone number from the test entry
410         assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", DirContext.REMOVE_ATTRIBUTE, changes ) );
411 
412         // Gives grantModify, and grantRead perm to all users in the Administrators group for
413         // entries and all attribute types and values
414         createAccessControlSubentry( "administratorModifyRemove", "{ " +
415                 "identificationTag \"addAci\", " +
416                 "precedence 14, " +
417                 "authenticationLevel none, " +
418                 "itemOrUserFirst userFirst: { " +
419                 "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
420                 "userPermissions { " +
421                         "{ protectedItems {entry}, grantsAndDenials { grantModify, grantBrowse } }, " +
422                         "{ protectedItems {allAttributeValues {telephoneNumber}}, grantsAndDenials { grantRemove } } " +
423                         "} } }" );
424 
425         // try a modify operation which should succeed with ACI and group membership change
426         assertTrue( checkCanModifyAs( "billyd", "billyd", "ou=testou", DirContext.REMOVE_ATTRIBUTE, changes ) );
427         deleteAccessControlSubentry( "administratorModifyRemove" );
428 
429         // ----------------------------------------------------------------------------------
430         // Modify with Attribute Replace (requires both grantRemove and grantAdd on attrs)
431         // ----------------------------------------------------------------------------------
432 
433         // now let's test to see if we can perform a modify with a delete op
434         changes = new BasicAttributes( "telephoneNumber", "867-5309", true );
435 
436         // make sure we cannot remove the telephone number from the test entry
437         assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", DirContext.REPLACE_ATTRIBUTE, changes ) );
438 
439         // Gives grantModify, and grantRead perm to all users in the Administrators group for
440         // entries and all attribute types and values
441         createAccessControlSubentry( "administratorModifyReplace", "{ " +
442                 "identificationTag \"addAci\", " +
443                 "precedence 14, " +
444                 "authenticationLevel none, " +
445                 "itemOrUserFirst userFirst: { " +
446                 "userClasses { userGroup { \"cn=Administrators,ou=groups,ou=system\" } }, " +
447                 "userPermissions { " +
448                         "{ protectedItems {entry}, grantsAndDenials { grantModify, grantBrowse } }, " +
449                         "{ protectedItems {allAttributeValues {telephoneNumber}}, grantsAndDenials { grantAdd, grantRemove } } " +
450                         "} } }" );
451 
452         // try a modify operation which should succeed with ACI and group membership change
453         assertTrue( checkCanModifyAs( "billyd", "billyd", "ou=testou", DirContext.REPLACE_ATTRIBUTE, changes ) );
454         deleteAccessControlSubentry( "administratorModifyReplace" );
455     }
456 
457 
458 //    /***
459 //     * Checks to make sure name based userClass works for modify operations.
460 //     *
461 //     * @throws javax.naming.NamingException if the test encounters an error
462 //     */
463 //    public void testGrantModifyByName() throws NamingException
464 //    {
465 //        // create the non-admin user
466 //        createUser( "billyd", "billyd" );
467 //
468 //        // try an modify operation which should fail without any ACI
469 //        assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
470 //
471 //        // now add a subentry that enables user billyd to modify an entry below ou=system
472 //        createAccessControlSubentry( "billydAdd", "{ " +
473 //                "identificationTag \"addAci\", " +
474 //                "precedence 14, " +
475 //                "authenticationLevel none, " +
476 //                "itemOrUserFirst userFirst: { " +
477 //                "userClasses { name { \"uid=billyd,ou=users,ou=system\" } }, " +
478 //                "userPermissions { { " +
479 //                "protectedItems {entry, allUserAttributeTypesAndValues}, " +
480 //                "grantsAndDenials { grantModify, grantRead, grantBrowse } } } } }" );
481 //
482 //        // should work now that billyd is authorized by name
483 //        assertTrue( checkCanModifyAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
484 //    }
485 //
486 //
487 //    /***
488 //     * Checks to make sure subtree based userClass works for modify operations.
489 //     *
490 //     * @throws javax.naming.NamingException if the test encounters an error
491 //     */
492 //    public void testGrantModifyBySubtree() throws NamingException
493 //    {
494 //        // create the non-admin user
495 //        createUser( "billyd", "billyd" );
496 //
497 //        // try a modify operation which should fail without any ACI
498 //        assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
499 //
500 //        // now add a subentry that enables user billyd to modify an entry below ou=system
501 //        createAccessControlSubentry( "billyAddBySubtree", "{ " +
502 //                "identificationTag \"addAci\", " +
503 //                "precedence 14, " +
504 //                "authenticationLevel none, " +
505 //                "itemOrUserFirst userFirst: { " +
506 //                "userClasses { subtree { { base \"ou=users,ou=system\" } } }, " +
507 //                "userPermissions { { " +
508 //                "protectedItems {entry, allUserAttributeTypesAndValues}, " +
509 //                "grantsAndDenials { grantModify, grantRead, grantBrowse } } } } }" );
510 //
511 //        // should work now that billyd is authorized by the subtree userClass
512 //        assertTrue( checkCanModifyAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
513 //    }
514 //
515 //
516 //    /***
517 //     * Checks to make sure <b>allUsers</b> userClass works for modify operations.
518 //     *
519 //     * @throws javax.naming.NamingException if the test encounters an error
520 //     */
521 //    public void testGrantModifyAllUsers() throws NamingException
522 //    {
523 //        // create the non-admin user
524 //        createUser( "billyd", "billyd" );
525 //
526 //        // try an add operation which should fail without any ACI
527 //        assertFalse( checkCanModifyAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
528 //
529 //        // now add a subentry that enables anyone to add an entry below ou=system
530 //        createAccessControlSubentry( "anybodyAdd", "{ " +
531 //                "identificationTag \"addAci\", " +
532 //                "precedence 14, " +
533 //                "authenticationLevel none, " +
534 //                "itemOrUserFirst userFirst: { " +
535 //                "userClasses { allUsers }, " +
536 //                "userPermissions { { " +
537 //                "protectedItems {entry, allUserAttributeTypesAndValues}, " +
538 //                "grantsAndDenials { grantModify, grantRead, grantBrowse } } } } }" );
539 //
540 //        // see if we can now modify that test entry's number which we could not before
541 //        // should work with billyd now that all users are authorized
542 //        assertTrue( checkCanModifyAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
543 //    }
544 }