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