1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.vote;
17  
18  import junit.framework.TestCase;
19  
20  import org.acegisecurity.AuthorizationServiceException;
21  import org.acegisecurity.ConfigAttributeDefinition;
22  import org.acegisecurity.MockAclManager;
23  import org.acegisecurity.SecurityConfig;
24  import org.acegisecurity.acl.AclEntry;
25  import org.acegisecurity.acl.AclManager;
26  import org.acegisecurity.acl.basic.MockAclObjectIdentity;
27  import org.acegisecurity.acl.basic.SimpleAclEntry;
28  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
29  import org.acegisecurity.util.SimpleMethodInvocation;
30  
31  import org.aopalliance.intercept.MethodInvocation;
32  
33  import org.aspectj.lang.JoinPoint;
34  
35  import java.lang.reflect.Method;
36  
37  
38  /***
39   * Tests {@link BasicAclEntryVoter}.
40   *
41   * @author Ben Alex
42   * @version $Id: BasicAclEntryVoterTests.java,v 1.4 2005/11/25 04:17:24 benalex Exp $
43   */
44  public class BasicAclEntryVoterTests extends TestCase {
45      //~ Constructors ===========================================================
46  
47      public BasicAclEntryVoterTests() {
48          super();
49      }
50  
51      public BasicAclEntryVoterTests(String arg0) {
52          super(arg0);
53      }
54  
55      //~ Methods ================================================================
56  
57      public final void setUp() throws Exception {
58          super.setUp();
59      }
60  
61      public static void main(String[] args) {
62          junit.textui.TestRunner.run(BasicAclEntryVoterTests.class);
63      }
64  
65      public void testNormalOperation() throws Exception {
66          // Setup a domain object subject of this test
67          SomeDomainObject domainObject = new SomeDomainObject("foo");
68  
69          // Setup an AclManager
70          AclManager aclManager = new MockAclManager(domainObject, "marissa",
71                  new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
72                          "marissa", new MockAclObjectIdentity(), null,
73                          SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
74                          "marissa", new MockAclObjectIdentity(), null,
75                          SimpleAclEntry.READ), new SimpleAclEntry("marissa",
76                          new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
77  
78          // Wire up a voter
79          BasicAclEntryVoter voter = new BasicAclEntryVoter();
80          voter.setAclManager(aclManager);
81          assertEquals(aclManager, voter.getAclManager());
82          voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
83          assertEquals("FOO_ADMIN_OR_WRITE_ACCESS",
84              voter.getProcessConfigAttribute());
85          voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
86          assertEquals(2, voter.getRequirePermission().length);
87          voter.setProcessDomainObjectClass(SomeDomainObject.class);
88          assertEquals(SomeDomainObject.class, voter.getProcessDomainObjectClass());
89          voter.afterPropertiesSet();
90  
91          // Wire up an invocation to be voted on
92          ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
93          attr.addConfigAttribute(new SecurityConfig("FOO_ADMIN_OR_WRITE_ACCESS"));
94  
95          // Setup a MockMethodInvocation, so voter can retrieve domainObject
96          MethodInvocation mi = getMethodInvocation(domainObject);
97  
98          assertEquals(AccessDecisionVoter.ACCESS_GRANTED,
99              voter.vote(
100                 new UsernamePasswordAuthenticationToken("marissa", null), mi,
101                 attr));
102     }
103 
104     public void testOnlySupportsMethodInvocation() {
105         BasicAclEntryVoter voter = new BasicAclEntryVoter();
106         assertTrue(voter.supports(MethodInvocation.class));
107         assertFalse(voter.supports(JoinPoint.class));
108     }
109 
110     public void testStartupRejectsMissingAclManager() throws Exception {
111         AclManager aclManager = new MockAclManager("domain1", "marissa",
112                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
113                         "marissa", new MockAclObjectIdentity(), null,
114                         SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
115                         "marissa", new MockAclObjectIdentity(), null,
116                         SimpleAclEntry.READ), new SimpleAclEntry("marissa",
117                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
118 
119         // Wire up a voter
120         BasicAclEntryVoter voter = new BasicAclEntryVoter();
121         voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
122         voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
123         voter.setProcessDomainObjectClass(SomeDomainObject.class);
124 
125         try {
126             voter.afterPropertiesSet();
127             fail("Should have thrown IllegalArgumentException");
128         } catch (IllegalArgumentException expected) {
129             assertTrue(true);
130         }
131     }
132 
133     public void testStartupRejectsMissingProcessConfigAttribute()
134         throws Exception {
135         AclManager aclManager = new MockAclManager("domain1", "marissa",
136                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
137                         "marissa", new MockAclObjectIdentity(), null,
138                         SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
139                         "marissa", new MockAclObjectIdentity(), null,
140                         SimpleAclEntry.READ), new SimpleAclEntry("marissa",
141                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
142 
143         // Wire up a voter
144         BasicAclEntryVoter voter = new BasicAclEntryVoter();
145         voter.setAclManager(aclManager);
146         voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
147         voter.setProcessDomainObjectClass(SomeDomainObject.class);
148 
149         try {
150             voter.afterPropertiesSet();
151             fail("Should have thrown IllegalArgumentException");
152         } catch (IllegalArgumentException expected) {
153             assertTrue(true);
154         }
155     }
156 
157     public void testStartupRejectsMissingProcessDomainObjectClass()
158         throws Exception {
159         BasicAclEntryVoter voter = new BasicAclEntryVoter();
160 
161         try {
162             voter.setProcessDomainObjectClass(null);
163             fail("Should have thrown IllegalArgumentException");
164         } catch (IllegalArgumentException expected) {
165             assertTrue(true);
166         }
167     }
168 
169     public void testStartupRejectsMissingRequirePermission()
170         throws Exception {
171         AclManager aclManager = new MockAclManager("domain1", "marissa",
172                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
173                         "marissa", new MockAclObjectIdentity(), null,
174                         SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
175                         "marissa", new MockAclObjectIdentity(), null,
176                         SimpleAclEntry.READ), new SimpleAclEntry("marissa",
177                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
178 
179         // Wire up a voter
180         BasicAclEntryVoter voter = new BasicAclEntryVoter();
181         voter.setAclManager(aclManager);
182         voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
183         voter.setProcessDomainObjectClass(SomeDomainObject.class);
184 
185         try {
186             voter.afterPropertiesSet();
187             fail("Should have thrown IllegalArgumentException");
188         } catch (IllegalArgumentException expected) {
189             assertTrue(true);
190         }
191     }
192 
193     public void testSupportsConfigAttribute() {
194         BasicAclEntryVoter voter = new BasicAclEntryVoter();
195         voter.setProcessConfigAttribute("foobar");
196         assertTrue(voter.supports(new SecurityConfig("foobar")));
197     }
198 
199     public void testVoterAbstainsIfDomainObjectIsNull()
200         throws Exception {
201         // Setup a domain object subject of this test
202         SomeDomainObject domainObject = new SomeDomainObject("foo");
203 
204         // Setup an AclManager
205         AclManager aclManager = new MockAclManager(domainObject, "marissa",
206                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
207                         "marissa", new MockAclObjectIdentity(), null,
208                         SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
209                         "marissa", new MockAclObjectIdentity(), null,
210                         SimpleAclEntry.READ), new SimpleAclEntry("marissa",
211                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
212 
213         // Wire up a voter
214         BasicAclEntryVoter voter = new BasicAclEntryVoter();
215         voter.setAclManager(aclManager);
216         voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
217         voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
218         voter.setProcessDomainObjectClass(SomeDomainObject.class);
219         voter.afterPropertiesSet();
220 
221         // Wire up an invocation to be voted on
222         ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
223         attr.addConfigAttribute(new SecurityConfig("A_DIFFERENT_ATTRIBUTE"));
224 
225         // Setup a MockMethodInvocation, so voter can retrieve domainObject
226         MethodInvocation mi = getMethodInvocation(domainObject);
227 
228         assertEquals(AccessDecisionVoter.ACCESS_ABSTAIN,
229             voter.vote(
230                 new UsernamePasswordAuthenticationToken("marissa", null), mi,
231                 attr));
232     }
233 
234     public void testVoterAbstainsIfNotMatchingConfigAttribute()
235         throws Exception {
236         // Setup a domain object subject of this test
237         SomeDomainObject domainObject = null;
238 
239         // Setup an AclManager
240         AclManager aclManager = new MockAclManager(domainObject, "marissa",
241                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
242                         "marissa", new MockAclObjectIdentity(), null,
243                         SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
244                         "marissa", new MockAclObjectIdentity(), null,
245                         SimpleAclEntry.READ), new SimpleAclEntry("marissa",
246                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
247 
248         // Wire up a voter
249         BasicAclEntryVoter voter = new BasicAclEntryVoter();
250         voter.setAclManager(aclManager);
251         voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
252         voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
253         voter.setProcessDomainObjectClass(SomeDomainObject.class);
254         voter.afterPropertiesSet();
255 
256         // Wire up an invocation to be voted on
257         ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
258         attr.addConfigAttribute(new SecurityConfig("FOO_ADMIN_OR_WRITE_ACCESS"));
259 
260         // Setup a MockMethodInvocation, so voter can retrieve domainObject
261         MethodInvocation mi = getMethodInvocation(domainObject);
262 
263         assertEquals(AccessDecisionVoter.ACCESS_ABSTAIN,
264             voter.vote(
265                 new UsernamePasswordAuthenticationToken("marissa", null), mi,
266                 attr));
267     }
268 
269     public void testVoterCanDenyAccessBasedOnInternalMethodOfDomainObject()
270         throws Exception {
271         // Setup a domain object subject of this test
272         SomeDomainObject domainObject = new SomeDomainObject("foo");
273 
274         // Setup an AclManager
275         AclManager aclManager = new MockAclManager(domainObject.getParent(),
276                 "marissa",
277                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
278                         "marissa", new MockAclObjectIdentity(), null,
279                         SimpleAclEntry.DELETE)});
280 
281         // Wire up a voter
282         BasicAclEntryVoter voter = new BasicAclEntryVoter();
283         voter.setAclManager(aclManager);
284         voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
285         voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
286         voter.setProcessDomainObjectClass(SomeDomainObject.class);
287         voter.setInternalMethod("getParent");
288         voter.afterPropertiesSet();
289 
290         // Wire up an invocation to be voted on
291         ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
292         attr.addConfigAttribute(new SecurityConfig("FOO_ADMIN_OR_WRITE_ACCESS"));
293 
294         // Setup a MockMethodInvocation, so voter can retrieve domainObject
295         MethodInvocation mi = getMethodInvocation(domainObject);
296 
297         assertEquals(AccessDecisionVoter.ACCESS_DENIED,
298             voter.vote(
299                 new UsernamePasswordAuthenticationToken("marissa", null), mi,
300                 attr));
301     }
302 
303     public void testVoterCanDenyAccessIfPrincipalHasNoPermissionsAtAllToDomainObject()
304         throws Exception {
305         // Setup a domain object subject of this test
306         SomeDomainObject domainObject = new SomeDomainObject("foo");
307 
308         // Setup an AclManager
309         AclManager aclManager = new MockAclManager(domainObject, "marissa",
310                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
311                         "marissa", new MockAclObjectIdentity(), null,
312                         SimpleAclEntry.DELETE)});
313 
314         // Wire up a voter
315         BasicAclEntryVoter voter = new BasicAclEntryVoter();
316         voter.setAclManager(aclManager);
317         voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
318         voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
319         voter.setProcessDomainObjectClass(SomeDomainObject.class);
320         voter.setInternalMethod("getParent");
321         voter.afterPropertiesSet();
322 
323         // Wire up an invocation to be voted on
324         ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
325         attr.addConfigAttribute(new SecurityConfig("FOO_ADMIN_OR_WRITE_ACCESS"));
326 
327         // Setup a MockMethodInvocation, so voter can retrieve domainObject
328         MethodInvocation mi = getMethodInvocation(domainObject);
329 
330         // NB: scott is the principal, not marissa
331         assertEquals(AccessDecisionVoter.ACCESS_DENIED,
332             voter.vote(new UsernamePasswordAuthenticationToken("scott", null),
333                 mi, attr));
334     }
335 
336     public void testVoterCanGrantAccessBasedOnInternalMethodOfDomainObject()
337         throws Exception {
338         // Setup a domain object subject of this test
339         SomeDomainObject domainObject = new SomeDomainObject("foo");
340 
341         // Setup an AclManager
342         AclManager aclManager = new MockAclManager(domainObject.getParent(),
343                 "marissa",
344                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
345                         "marissa", new MockAclObjectIdentity(), null,
346                         SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
347                         "marissa", new MockAclObjectIdentity(), null,
348                         SimpleAclEntry.READ), new SimpleAclEntry("marissa",
349                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
350 
351         // Wire up a voter
352         BasicAclEntryVoter voter = new BasicAclEntryVoter();
353         voter.setAclManager(aclManager);
354         voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
355         voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
356         voter.setProcessDomainObjectClass(SomeDomainObject.class);
357         voter.setInternalMethod("getParent");
358         assertEquals("getParent", voter.getInternalMethod());
359         voter.afterPropertiesSet();
360 
361         // Wire up an invocation to be voted on
362         ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
363         attr.addConfigAttribute(new SecurityConfig("FOO_ADMIN_OR_WRITE_ACCESS"));
364 
365         // Setup a MockMethodInvocation, so voter can retrieve domainObject
366         // (well actually it will access domainObject.getParent())
367         MethodInvocation mi = getMethodInvocation(domainObject);
368 
369         assertEquals(AccessDecisionVoter.ACCESS_GRANTED,
370             voter.vote(
371                 new UsernamePasswordAuthenticationToken("marissa", null), mi,
372                 attr));
373     }
374 
375     public void testVoterThrowsExceptionIfInvalidInternalMethodOfDomainObject()
376         throws Exception {
377         // Setup a domain object subject of this test
378         SomeDomainObject domainObject = new SomeDomainObject("foo");
379 
380         // Setup an AclManager
381         AclManager aclManager = new MockAclManager(domainObject.getParent(),
382                 "marissa",
383                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
384                         "marissa", new MockAclObjectIdentity(), null,
385                         SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
386                         "marissa", new MockAclObjectIdentity(), null,
387                         SimpleAclEntry.READ), new SimpleAclEntry("marissa",
388                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
389 
390         // Wire up a voter
391         BasicAclEntryVoter voter = new BasicAclEntryVoter();
392         voter.setAclManager(aclManager);
393         voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
394         voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
395         voter.setProcessDomainObjectClass(SomeDomainObject.class);
396         voter.setInternalMethod("getNonExistentParentName");
397         voter.afterPropertiesSet();
398 
399         // Wire up an invocation to be voted on
400         ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
401         attr.addConfigAttribute(new SecurityConfig("FOO_ADMIN_OR_WRITE_ACCESS"));
402 
403         // Setup a MockMethodInvocation, so voter can retrieve domainObject
404         // (well actually it will access domainObject.getParent())
405         MethodInvocation mi = getMethodInvocation(domainObject);
406 
407         try {
408             voter.vote(new UsernamePasswordAuthenticationToken("marissa", null),
409                 mi, attr);
410             fail("Should have thrown AuthorizationServiceException");
411         } catch (AuthorizationServiceException expected) {
412             assertTrue(true);
413         }
414     }
415 
416     public void testVoterThrowsExceptionIfProcessDomainObjectNotFound()
417         throws Exception {
418         // Setup a domain object subject of this test
419         SomeDomainObject domainObject = new SomeDomainObject("foo");
420 
421         // Setup an AclManager
422         AclManager aclManager = new MockAclManager(domainObject.getParent(),
423                 "marissa",
424                 new AclEntry[] {new MockAclEntry(), new SimpleAclEntry(
425                         "marissa", new MockAclObjectIdentity(), null,
426                         SimpleAclEntry.ADMINISTRATION), new SimpleAclEntry(
427                         "marissa", new MockAclObjectIdentity(), null,
428                         SimpleAclEntry.READ), new SimpleAclEntry("marissa",
429                         new MockAclObjectIdentity(), null, SimpleAclEntry.DELETE)});
430 
431         // Wire up a voter
432         BasicAclEntryVoter voter = new BasicAclEntryVoter();
433         voter.setAclManager(aclManager);
434         voter.setProcessConfigAttribute("FOO_ADMIN_OR_WRITE_ACCESS");
435         voter.setRequirePermission(new int[] {SimpleAclEntry.ADMINISTRATION, SimpleAclEntry.WRITE});
436         voter.setProcessDomainObjectClass(SomeDomainObject.class);
437         voter.afterPropertiesSet();
438 
439         // Wire up an invocation to be voted on
440         ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
441         attr.addConfigAttribute(new SecurityConfig("FOO_ADMIN_OR_WRITE_ACCESS"));
442 
443         // Setup a MockMethodInvocation that doesn't provide SomeDomainObject arg
444         Class clazz = String.class;
445         Method method = clazz.getMethod("toString", new Class[] {});
446 
447         MethodInvocation mi = new SimpleMethodInvocation(method,
448                 new Object[] {domainObject});
449 
450         try {
451             voter.vote(new UsernamePasswordAuthenticationToken("marissa", null),
452                 mi, attr);
453             fail("Should have thrown AuthorizationServiceException");
454         } catch (AuthorizationServiceException expected) {
455             assertTrue(true);
456         }
457     }
458 
459     private MethodInvocation getMethodInvocation(SomeDomainObject domainObject)
460         throws Exception {
461         Class clazz = SomeDomainObjectManager.class;
462         Method method = clazz.getMethod("someServiceMethod",
463                 new Class[] {SomeDomainObject.class});
464 
465         return new SimpleMethodInvocation(method, new Object[] {domainObject});
466     }
467 
468     //~ Inner Classes ==========================================================
469 
470     private class MockAclEntry implements AclEntry {
471         // just so AclTag iterates some different types of AclEntrys
472     }
473 }