    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.intercept.method.aopalliance;
    17
    18  import java.lang.reflect.Method;
    19  import java.util.Iterator;
    20
    21  import junit.framework.TestCase;
    22
    23  import org.acegisecurity.AccessDecisionManager;
    24  import org.acegisecurity.AccessDeniedException;
    25  import org.acegisecurity.AfterInvocationManager;
    26  import org.acegisecurity.Authentication;
    27  import org.acegisecurity.AuthenticationCredentialsNotFoundException;
    28  import org.acegisecurity.AuthenticationException;
    29  import org.acegisecurity.ConfigAttribute;
    30  import org.acegisecurity.ConfigAttributeDefinition;
    31  import org.acegisecurity.GrantedAuthority;
    32  import org.acegisecurity.GrantedAuthorityImpl;
    33  import org.acegisecurity.ITargetObject;
    34  import org.acegisecurity.MockAccessDecisionManager;
    35  import org.acegisecurity.MockAfterInvocationManager;
    36  import org.acegisecurity.MockAuthenticationManager;
    37  import org.acegisecurity.MockRunAsManager;
    38  import org.acegisecurity.RunAsManager;
    39  import org.acegisecurity.context.SecurityContextHolder;
    40  import org.acegisecurity.intercept.method.AbstractMethodDefinitionSource;
    41  import org.acegisecurity.intercept.method.MockMethodDefinitionSource;
    42  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
    43  import org.acegisecurity.runas.RunAsManagerImpl;
    44  import org.springframework.context.ApplicationContext;
    45  import org.springframework.context.support.ClassPathXmlApplicationContext;
    46
    47
    48  /***
    49   * Tests {@link MethodSecurityInterceptor}.
    50   *
    51   * @author Ben Alex
    52   * @version $Id: MethodSecurityInterceptorTests.java,v 1.12 2005/11/30 01:23:36 benalex Exp $
    53   */
    54  public class MethodSecurityInterceptorTests extends TestCase {
    55      //~ Constructors ===========================================================
    56
    57      public MethodSecurityInterceptorTests() {
    58          super();
    59      }
    60
    61      public MethodSecurityInterceptorTests(String arg0) {
    62          super(arg0);
    63      }
    64
    65      //~ Methods ================================================================
    66
    67      public final void setUp() throws Exception {
    68          super.setUp();
    69          SecurityContextHolder.getContext().setAuthentication(null);
    70      }
    71
    72      public static void main(String[] args) {
    73          junit.textui.TestRunner.run(MethodSecurityInterceptorTests.class);
    74      }
    75
    76      public void testCallingAPublicMethodFacadeWillNotRepeatSecurityChecksWhenPassedToTheSecuredMethodItFronts()
    77          throws Exception {
    78          ITargetObject target = makeInterceptedTarget();
    79          String result = target.publicMakeLowerCase("HELLO");
    80          assertEquals("hello Authentication empty", result);
    81      }
    82
    83      public void testCallingAPublicMethodWhenPresentingAnAuthenticationObjectWillNotChangeItsIsAuthenticatedProperty()
    84          throws Exception {
    85          UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test",
    86                  "Password");
    87          assertTrue(!token.isAuthenticated());
    88          SecurityContextHolder.getContext().setAuthentication(token);
    89
    90          // The associated MockAuthenticationManager WILL accept the above UsernamePasswordAuthenticationToken
    91          ITargetObject target = makeInterceptedTarget();
    92          String result = target.publicMakeLowerCase("HELLO");
    93          assertEquals("hello org.acegisecurity.providers.UsernamePasswordAuthenticationToken false",
    94              result);
    95      }
    96
    97      public void testDeniesWhenAppropriate() throws Exception {
    98          UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test",
    99                  "Password",
    100                 new GrantedAuthority[] {new GrantedAuthorityImpl("MOCK_NO_BENEFIT_TO_THIS_GRANTED_AUTHORITY")});
    101         SecurityContextHolder.getContext().setAuthentication(token);
    102
    103         ITargetObject target = makeInterceptedTarget();
    104
    105         try {
    106             target.makeUpperCase("HELLO");
    107             fail("Should have thrown AccessDeniedException");
    108         } catch (AccessDeniedException expected) {
    109             assertTrue(true);
    110         }
    111     }
    112
    113     public void testGetters() {
    114         MockAccessDecisionManager accessDecision = new MockAccessDecisionManager();
    115         MockRunAsManager runAs = new MockRunAsManager();
    116         MockAuthenticationManager authManager = new MockAuthenticationManager();
    117         MockMethodDefinitionSource methodSource = new MockMethodDefinitionSource(false,
    118                 true);
    119         MockAfterInvocationManager afterInvocation = new MockAfterInvocationManager();
    120
    121         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    122         si.setAccessDecisionManager(accessDecision);
    123         si.setRunAsManager(runAs);
    124         si.setAuthenticationManager(authManager);
    125         si.setObjectDefinitionSource(methodSource);
    126         si.setAfterInvocationManager(afterInvocation);
    127
    128         assertEquals(accessDecision, si.getAccessDecisionManager());
    129         assertEquals(runAs, si.getRunAsManager());
    130         assertEquals(authManager, si.getAuthenticationManager());
    131         assertEquals(methodSource, si.getObjectDefinitionSource());
    132         assertEquals(afterInvocation, si.getAfterInvocationManager());
    133     }
    134
    135     public void testMethodCallWithRunAsReplacement() throws Exception {
    136         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test",
    137                 "Password",
    138                 new GrantedAuthority[] {new GrantedAuthorityImpl("MOCK_UPPER")});
    139         SecurityContextHolder.getContext().setAuthentication(token);
    140
    141         ITargetObject target = makeInterceptedTarget();
    142         String result = target.makeUpperCase("hello");
    143         assertEquals("HELLO org.acegisecurity.MockRunAsAuthenticationToken true",
    144             result);
    145     }
    146
    147     public void testMethodCallWithoutRunAsReplacement()
    148         throws Exception {
    149         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test",
    150                 "Password",
    151                 new GrantedAuthority[] {new GrantedAuthorityImpl("MOCK_LOWER")});
    152         assertTrue(token.isAuthenticated());
    153         SecurityContextHolder.getContext().setAuthentication(token);
    154
    155         ITargetObject target = makeInterceptedTargetWithoutAnAfterInvocationManager();
    156         String result = target.makeLowerCase("HELLO");
    157
    158         // Note we check the isAuthenticated remained true in following line
    159         assertEquals("hello org.acegisecurity.providers.UsernamePasswordAuthenticationToken true",
    160             result);
    161     }
    162
    163     public void testRejectionOfEmptySecurityContext() throws Exception {
    164         ITargetObject target = makeInterceptedTarget();
    165
    166         try {
    167             target.makeUpperCase("hello");
    168             fail(
    169                 "Should have thrown AuthenticationCredentialsNotFoundException");
    170         } catch (AuthenticationCredentialsNotFoundException expected) {
    171             assertTrue(true);
    172         }
    173     }
    174
    175     public void testRejectsAccessDecisionManagersThatDoNotSupportMethodInvocation()
    176         throws Exception {
    177         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    178         si.setAccessDecisionManager(new MockAccessDecisionManagerWhichOnlySupportsStrings());
    179         si.setAuthenticationManager(new MockAuthenticationManager());
    180         si.setObjectDefinitionSource(new MockMethodDefinitionSource(false, true));
    181         si.setRunAsManager(new MockRunAsManager());
    182
    183         try {
    184             si.afterPropertiesSet();
    185             fail("Should have thrown IllegalArgumentException");
    186         } catch (IllegalArgumentException expected) {
    187             assertEquals("AccessDecisionManager does not support secure object class: interface org.aopalliance.intercept.MethodInvocation",
    188                 expected.getMessage());
    189         }
    190     }
    191
    192     public void testRejectsCallsWhenAuthenticationIsIncorrect()
    193         throws Exception {
    194         UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test",
    195                 "Password");
    196         assertTrue(!token.isAuthenticated());
    197         SecurityContextHolder.getContext().setAuthentication(token);
    198
    199         // NB: The associated MockAuthenticationManager WILL reject the above UsernamePasswordAuthenticationToken
    200         ITargetObject target = makeInterceptedTargetRejectsAuthentication();
    201
    202         try {
    203             target.makeLowerCase("HELLO");
    204             fail("Should have thrown AuthenticationException");
    205         } catch (AuthenticationException expected) {
    206             assertTrue(true);
    207         }
    208     }
    209
    210     public void testRejectsCallsWhenObjectDefinitionSourceDoesNotSupportObject()
    211         throws Throwable {
    212         MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
    213         interceptor.setObjectDefinitionSource(new MockObjectDefinitionSourceWhichOnlySupportsStrings());
    214         interceptor.setAccessDecisionManager(new MockAccessDecisionManager());
    215         interceptor.setAuthenticationManager(new MockAuthenticationManager());
    216         interceptor.setRunAsManager(new MockRunAsManager());
    217
    218         try {
    219             interceptor.afterPropertiesSet();
    220             fail("Should have thrown IllegalArgumentException");
    221         } catch (IllegalArgumentException expected) {
    222             assertEquals("ObjectDefinitionSource does not support secure object class: interface org.aopalliance.intercept.MethodInvocation",
    223                 expected.getMessage());
    224         }
    225     }
    226
    227     public void testRejectsCallsWhenObjectIsNull() throws Throwable {
    228         MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
    229
    230         try {
    231             interceptor.invoke(null);
    232             fail("Should have thrown IllegalArgumentException");
    233         } catch (IllegalArgumentException expected) {
    234             assertEquals("Object was null", expected.getMessage());
    235         }
    236     }
    237
    238     public void testRejectsRunAsManagersThatDoNotSupportMethodInvocation()
    239         throws Exception {
    240         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    241         si.setAccessDecisionManager(new MockAccessDecisionManager());
    242         si.setAuthenticationManager(new MockAuthenticationManager());
    243         si.setObjectDefinitionSource(new MockMethodDefinitionSource(false, true));
    244         si.setRunAsManager(new MockRunAsManagerWhichOnlySupportsStrings());
    245         si.setAfterInvocationManager(new MockAfterInvocationManager());
    246
    247         try {
    248             si.afterPropertiesSet();
    249             fail("Should have thrown IllegalArgumentException");
    250         } catch (IllegalArgumentException expected) {
    251             assertEquals("RunAsManager does not support secure object class: interface org.aopalliance.intercept.MethodInvocation",
    252                 expected.getMessage());
    253         }
    254     }
    255
    256     public void testStartupCheckForAccessDecisionManager()
    257         throws Exception {
    258         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    259         si.setRunAsManager(new MockRunAsManager());
    260         si.setAuthenticationManager(new MockAuthenticationManager());
    261         si.setAfterInvocationManager(new MockAfterInvocationManager());
    262
    263         si.setObjectDefinitionSource(new MockMethodDefinitionSource(false, true));
    264
    265         try {
    266             si.afterPropertiesSet();
    267             fail("Should have thrown IllegalArgumentException");
    268         } catch (IllegalArgumentException expected) {
    269             assertEquals("An AccessDecisionManager is required",
    270                 expected.getMessage());
    271         }
    272     }
    273
    274     public void testStartupCheckForAuthenticationManager()
    275         throws Exception {
    276         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    277         si.setAccessDecisionManager(new MockAccessDecisionManager());
    278         si.setRunAsManager(new MockRunAsManager());
    279         si.setAfterInvocationManager(new MockAfterInvocationManager());
    280
    281         si.setObjectDefinitionSource(new MockMethodDefinitionSource(false, true));
    282
    283         try {
    284             si.afterPropertiesSet();
    285             fail("Should have thrown IllegalArgumentException");
    286         } catch (IllegalArgumentException expected) {
    287             assertEquals("An AuthenticationManager is required",
    288                 expected.getMessage());
    289         }
    290     }
    291
    292     public void testStartupCheckForMethodDefinitionSource()
    293         throws Exception {
    294         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    295         si.setAccessDecisionManager(new MockAccessDecisionManager());
    296         si.setAuthenticationManager(new MockAuthenticationManager());
    297
    298         try {
    299             si.afterPropertiesSet();
    300             fail("Should have thrown IllegalArgumentException");
    301         } catch (IllegalArgumentException expected) {
    302             assertEquals("An ObjectDefinitionSource is required",
    303                 expected.getMessage());
    304         }
    305     }
    306
    307     public void testStartupCheckForRunAsManager() throws Exception {
    308         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    309         si.setAccessDecisionManager(new MockAccessDecisionManager());
    310         si.setAuthenticationManager(new MockAuthenticationManager());
    311         si.setRunAsManager(null); // Overriding the default
    312
    313         si.setObjectDefinitionSource(new MockMethodDefinitionSource(false, true));
    314
    315         try {
    316             si.afterPropertiesSet();
    317             fail("Should have thrown IllegalArgumentException");
    318         } catch (IllegalArgumentException expected) {
    319             assertEquals("A RunAsManager is required", expected.getMessage());
    320         }
    321     }
    322
    323     public void testStartupCheckForValidAfterInvocationManager()
    324         throws Exception {
    325         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    326         si.setRunAsManager(new MockRunAsManager());
    327         si.setAuthenticationManager(new MockAuthenticationManager());
    328         si.setAfterInvocationManager(new MockAfterInvocationManagerWhichOnlySupportsStrings());
    329         si.setAccessDecisionManager(new MockAccessDecisionManager());
    330         si.setObjectDefinitionSource(new MockMethodDefinitionSource(false, true));
    331
    332         try {
    333             si.afterPropertiesSet();
    334             fail("Should have thrown IllegalArgumentException");
    335         } catch (IllegalArgumentException expected) {
    336             assertTrue(expected.getMessage().startsWith("AfterInvocationManager does not support secure object class:"));
    337         }
    338     }
    339
    340     public void testValidationFailsIfInvalidAttributePresented()
    341         throws Exception {
    342         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    343         si.setAccessDecisionManager(new MockAccessDecisionManager());
    344         si.setAuthenticationManager(new MockAuthenticationManager());
    345         si.setRunAsManager(new RunAsManagerImpl());
    346
    347         assertTrue(si.isValidateConfigAttributes()); // check default
    348         si.setObjectDefinitionSource(new MockMethodDefinitionSource(true, true));
    349
    350         try {
    351             si.afterPropertiesSet();
    352             fail("Should have thrown IllegalArgumentException");
    353         } catch (IllegalArgumentException expected) {
    354             assertEquals("Unsupported configuration attributes: [ANOTHER_INVALID, INVALID_ATTRIBUTE]",
    355                 expected.getMessage());
    356         }
    357     }
    358
    359     public void testValidationNotAttemptedIfIsValidateConfigAttributesSetToFalse()
    360         throws Exception {
    361         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    362         si.setAccessDecisionManager(new MockAccessDecisionManager());
    363         si.setAuthenticationManager(new MockAuthenticationManager());
    364
    365         assertTrue(si.isValidateConfigAttributes()); // check default
    366         si.setValidateConfigAttributes(false);
    367         assertTrue(!si.isValidateConfigAttributes()); // check changed
    368
    369         si.setObjectDefinitionSource(new MockMethodDefinitionSource(true, true));
    370         si.afterPropertiesSet();
    371         assertTrue(true);
    372     }
    373
    374     public void testValidationNotAttemptedIfMethodDefinitionSourceCannotReturnIterator()
    375         throws Exception {
    376         MethodSecurityInterceptor si = new MethodSecurityInterceptor();
    377         si.setAccessDecisionManager(new MockAccessDecisionManager());
    378         si.setRunAsManager(new MockRunAsManager());
    379         si.setAuthenticationManager(new MockAuthenticationManager());
    380
    381         assertTrue(si.isValidateConfigAttributes()); // check default
    382         si.setObjectDefinitionSource(new MockMethodDefinitionSource(true, false));
    383         si.afterPropertiesSet();
    384         assertTrue(true);
    385     }
    386
    387     private ITargetObject makeInterceptedTarget() {
    388         ApplicationContext context = new ClassPathXmlApplicationContext(
    389                 "org/acegisecurity/intercept/method/aopalliance/applicationContext.xml");
    390
    391         return (ITargetObject) context.getBean("target");
    392     }
    393
    394     private ITargetObject makeInterceptedTargetRejectsAuthentication() {
    395         ApplicationContext context = new ClassPathXmlApplicationContext(
    396                 "org/acegisecurity/intercept/method/aopalliance/applicationContext.xml");
    397
    398         MockAuthenticationManager authenticationManager = new MockAuthenticationManager(false);
    399         MethodSecurityInterceptor si = (MethodSecurityInterceptor) context
    400             .getBean("securityInterceptor");
    401         si.setAuthenticationManager(authenticationManager);
    402
    403         return (ITargetObject) context.getBean("target");
    404     }
    405
    406     private ITargetObject makeInterceptedTargetWithoutAnAfterInvocationManager() {
    407         ApplicationContext context = new ClassPathXmlApplicationContext(
    408                 "org/acegisecurity/intercept/method/aopalliance/applicationContext.xml");
    409
    410         MethodSecurityInterceptor si = (MethodSecurityInterceptor) context
    411             .getBean("securityInterceptor");
    412         si.setAfterInvocationManager(null);
    413
    414         return (ITargetObject) context.getBean("target");
    415     }
    416
    417     //~ Inner Classes ==========================================================
    418
    419     private class MockAccessDecisionManagerWhichOnlySupportsStrings
    420         implements AccessDecisionManager {
    421         public void decide(Authentication authentication, Object object,
    422             ConfigAttributeDefinition config) throws AccessDeniedException {
    423             throw new UnsupportedOperationException(
    424                 "mock method not implemented");
    425         }
    426
    427         public boolean supports(Class clazz) {
    428             if (String.class.isAssignableFrom(clazz)) {
    429                 return true;
    430             } else {
    431                 return false;
    432             }
    433         }
    434
    435         public boolean supports(ConfigAttribute attribute) {
    436             return true;
    437         }
    438     }
    439
    440     private class MockAfterInvocationManagerWhichOnlySupportsStrings
    441         implements AfterInvocationManager {
    442         public Object decide(Authentication authentication, Object object,
    443             ConfigAttributeDefinition config, Object returnedObject)
    444             throws AccessDeniedException {
    445             throw new UnsupportedOperationException(
    446                 "mock method not implemented");
    447         }
    448
    449         public boolean supports(Class clazz) {
    450             if (String.class.isAssignableFrom(clazz)) {
    451                 return true;
    452             } else {
    453                 return false;
    454             }
    455         }
    456
    457         public boolean supports(ConfigAttribute attribute) {
    458             return true;
    459         }
    460     }
    461
    462     private class MockObjectDefinitionSourceWhichOnlySupportsStrings
    463         extends AbstractMethodDefinitionSource {
    464         public Iterator getConfigAttributeDefinitions() {
    465             return null;
    466         }
    467
    468         public boolean supports(Class clazz) {
    469             if (String.class.isAssignableFrom(clazz)) {
    470                 return true;
    471             } else {
    472                 return false;
    473             }
    474         }
    475
    476         protected ConfigAttributeDefinition lookupAttributes(Method method) {
    477             throw new UnsupportedOperationException(
    478                 "mock method not implemented");
    479         }
    480     }
    481
    482     private class MockRunAsManagerWhichOnlySupportsStrings
    483         implements RunAsManager {
    484         public Authentication buildRunAs(Authentication authentication,
    485             Object object, ConfigAttributeDefinition config) {
    486             throw new UnsupportedOperationException(
    487                 "mock method not implemented");
    488         }
    489
    490         public boolean supports(Class clazz) {
    491             if (String.class.isAssignableFrom(clazz)) {
    492                 return true;
    493             } else {
    494                 return false;
    495             }
    496         }
    497
    498         public boolean supports(ConfigAttribute attribute) {
    499             return true;
    500         }
    501     }
    502 }