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 }