    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 }