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.authz;
21  
22  
23  import org.apache.directory.server.core.jndi.ServerLdapContext;
24  import org.apache.directory.server.core.integ.CiRunner;
25  import org.apache.directory.server.core.integ.annotations.Factory;
26  import org.apache.directory.server.core.DirectoryService;
27  import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
28  import org.apache.directory.shared.ldap.name.LdapDN;
29  import org.junit.runner.RunWith;
30  
31  import javax.naming.directory.Attribute;
32  import javax.naming.directory.Attributes;
33  import javax.naming.directory.BasicAttribute;
34  import javax.naming.directory.BasicAttributes;
35  import javax.naming.directory.DirContext;
36  import static org.junit.Assert.assertTrue;
37  import static org.junit.Assert.assertFalse;
38  import org.junit.Test;
39  import static org.apache.directory.server.core.authz.AutzIntegUtils.createUser;
40  import static org.apache.directory.server.core.authz.AutzIntegUtils.getContextAs;
41  import static org.apache.directory.server.core.authz.AutzIntegUtils.getContextAsAdmin;
42  import static org.apache.directory.server.core.authz.AutzIntegUtils.createAccessControlSubentry;
43  import static org.apache.directory.server.core.authz.AutzIntegUtils.addUserToGroup;
44  
45  
46  /**
47   * Tests whether or not authorization around entry compare operations work properly.
48   *
49   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
50   * @version $Rev: 691024 $
51   */
52  @RunWith ( CiRunner.class )
53  @Factory ( AutzIntegUtils.ServiceFactory.class )
54  public class CompareAuthorizationIT
55  {
56      public static DirectoryService service;
57  
58  
59      /**
60       * Checks if an attribute of a simple entry (an organizationalUnit's telephoneNumber)
61       * with an RDN relative to ou=system can be compared by a specific non-admin user.
62       * If a permission exception is encountered it is caught and false is returned,
63       * otherwise true is returned.  The entry is deleted after being created just in case
64       * subsequent calls to this method are made in the same test case: the admin account
65       * is used to add and delete this test entry so permissions to add and delete are not
66       * required to test the compare operation by the user.
67       *
68       * @param uid the unique identifier for the user (presumed to exist under ou=users,ou=system)
69       * @param password the password of this user
70       * @param entryRdn the relative DN, relative to ou=system where entry is created
71       * for comparison test
72       * @param number the telephone number to compare to this one
73       * @return true if the entry's telephoneNumber can be compared by the user at the
74       * specified location, false otherwise.  A false compare result still returns
75       * true.
76       * @throws javax.naming.NamingException if there are problems conducting the test
77       */
78      public boolean checkCanCompareTelephoneNumberAs( String uid, String password, String entryRdn, String number )
79          throws Exception
80      {
81          // create the entry with the telephoneNumber attribute to compare
82          Attributes testEntry = new BasicAttributes( "ou", "testou", true );
83          Attribute objectClass = new BasicAttribute( "objectClass" );
84          testEntry.put( objectClass );
85          objectClass.add( "top" );
86          objectClass.add( "organizationalUnit" );
87          testEntry.put( "telephoneNumber", "867-5309" ); // jenny don't change your number
88  
89          DirContext adminContext = getContextAsAdmin();
90  
91          try
92          {
93              // create the entry as admin
94              LdapDN userName = new LdapDN( "uid=" + uid + ",ou=users,ou=system" );
95              adminContext.createSubcontext( entryRdn, testEntry );
96  
97              // compare the telephone numbers
98              DirContext userContext = getContextAs( userName, password );
99              ServerLdapContext ctx = ( ServerLdapContext ) userContext.lookup( "" );
100             ctx.compare( new LdapDN( entryRdn + ",ou=system" ), "telephoneNumber", number );
101 
102             // don't return compare result which can be false but true since op was permitted
103             return true;
104         }
105         catch ( LdapNoPermissionException e )
106         {
107             return false;
108         }
109         finally
110         {
111             // let's clean up
112             adminContext.destroySubcontext( entryRdn );
113         }
114     }
115 
116 
117     /**
118      * Checks to make sure group membership based userClass works for compare operations.
119      *
120      * @throws javax.naming.NamingException if the test encounters an error
121      */
122     @Test
123     public void testGrantCompareAdministrators() throws Exception
124     {
125         // create the non-admin user
126         createUser( "billyd", "billyd" );
127 
128         // try a compare operation which should fail without any ACI
129         assertFalse( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
130 
131         // Gives grantCompare, and grantRead perm to all users in the Administrators group for
132         // entries and all attribute types and values
133         createAccessControlSubentry( "administratorAdd", 
134             "{ identificationTag \"addAci\", " +
135             "  precedence 14, " +
136             "  authenticationLevel none, " + 
137             "  itemOrUserFirst userFirst: { " +
138             "    userClasses { " +
139             "      userGroup { " +
140             "        \"cn=Administrators,ou=groups,ou=system\" " +
141             "      } " +
142             "    }, " + 
143             "    userPermissions { " +
144             "      { " +
145             "        protectedItems { entry, allUserAttributeTypesAndValues }, " +
146             "        grantsAndDenials { grantCompare, grantRead, grantBrowse } " +
147             "      } " +
148             "    } " +
149             "  } " +
150             "}" );
151 
152         // see if we can now add that test entry which we could not before
153         // add op should still fail since billd is not in the admin group
154         assertFalse( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
155 
156         // now add billyd to the Administrator group and try again
157         addUserToGroup( "billyd", "Administrators" );
158 
159         // try an add operation which should succeed with ACI and group membership change
160         assertTrue( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "976-6969" ) );
161     }
162 
163 
164     /**
165      * Checks to make sure name based userClass works for compare operations.
166      *
167      * @throws javax.naming.NamingException if the test encounters an error
168      */
169     @Test
170     public void testGrantCompareByName() throws Exception
171     {
172         // create the non-admin user
173         createUser( "billyd", "billyd" );
174 
175         // try an compare operation which should fail without any ACI
176         assertFalse( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
177 
178         // now add a subentry that enables user billyd to compare an entry below ou=system
179         createAccessControlSubentry( "billydAdd", 
180             "{ " +
181             "  identificationTag \"addAci\", precedence 14, authenticationLevel none, itemOrUserFirst userFirst: " + 
182             "  { " +
183             "    userClasses " +
184             "    { " +
185             "      name " +
186             "      { " +
187             "        \"uid=billyd,ou=users,ou=system\" " +
188             "      } " +
189             "    }, " +
190             "    userPermissions " +
191             "    { " +
192             "      { " +
193             "        protectedItems " +
194             "        {" +
195             "          entry, allUserAttributeTypesAndValues" +
196             "        }, " +
197             "        grantsAndDenials " +
198             "        { " +
199             "          grantCompare, grantRead, grantBrowse " +
200             "        } " +
201             "      } " +
202             "    } " +
203             "  } " +
204             "}" );
205 
206         // should work now that billyd is authorized by name
207         assertTrue( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
208     }
209 
210 
211     /**
212      * Checks to make sure subtree based userClass works for compare operations.
213      *
214      * @throws javax.naming.NamingException if the test encounters an error
215      */
216     @Test
217     public void testGrantCompareBySubtree() throws Exception
218     {
219         // create the non-admin user
220         createUser( "billyd", "billyd" );
221 
222         // try a compare operation which should fail without any ACI
223         assertFalse( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
224 
225         // now add a subentry that enables user billyd to compare an entry below ou=system
226         createAccessControlSubentry( "billyAddBySubtree", "{ " + "identificationTag \"addAci\", " + "precedence 14, "
227             + "authenticationLevel none, " + "itemOrUserFirst userFirst: { "
228             + "userClasses { subtree { { base \"ou=users,ou=system\" } } }, " + "userPermissions { { "
229             + "protectedItems {entry, allUserAttributeTypesAndValues}, "
230             + "grantsAndDenials { grantCompare, grantRead, grantBrowse } } } } }" );
231 
232         // should work now that billyd is authorized by the subtree userClass
233         assertTrue( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
234     }
235 
236 
237     /**
238      * Checks to make sure <b>allUsers</b> userClass works for compare operations.
239      *
240      * @throws javax.naming.NamingException if the test encounters an error
241      */
242     @Test
243     public void testGrantCompareAllUsers() throws Exception
244     {
245         // create the non-admin user
246         createUser( "billyd", "billyd" );
247 
248         // try an add operation which should fail without any ACI
249         assertFalse( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
250 
251         // now add a subentry that enables anyone to add an entry below ou=system
252         createAccessControlSubentry( "anybodyAdd", "{ " + "identificationTag \"addAci\", " + "precedence 14, "
253             + "authenticationLevel none, " + "itemOrUserFirst userFirst: { " + "userClasses { allUsers }, "
254             + "userPermissions { { " + "protectedItems {entry, allUserAttributeTypesAndValues}, "
255             + "grantsAndDenials { grantCompare, grantRead, grantBrowse } } } } }" );
256 
257         // see if we can now compare that test entry's number which we could not before
258         // should work with billyd now that all users are authorized
259         assertTrue( checkCanCompareTelephoneNumberAs( "billyd", "billyd", "ou=testou", "867-5309" ) );
260     }
261 
262 
263     @Test
264     public void testPasswordCompare() throws Exception
265     {
266         DirContext adminCtx = getContextAsAdmin();
267         Attributes user = new BasicAttributes( "uid", "bob", true );
268         user.put( "userPassword", "bobspassword" );
269         Attribute objectClass = new BasicAttribute( "objectClass" );
270         user.put( objectClass );
271         objectClass.add( "top" );
272         objectClass.add( "person" );
273         objectClass.add( "organizationalPerson" );
274         objectClass.add( "inetOrgPerson" );
275         user.put( "sn", "bob" );
276         user.put( "cn", "bob" );
277         adminCtx.createSubcontext( "uid=bob,ou=users", user );
278 
279         ServerLdapContext ctx = ( ServerLdapContext ) adminCtx.lookup( "" );
280         assertTrue( ctx.compare( new LdapDN( "uid=bob,ou=users,ou=system" ), "userPassword", "bobspassword" ) );
281     }
282 
283 }