/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.access.intercept.aspectj;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.CodeSignature;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.security.TargetObject;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.intercept.AfterInvocationManager;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.RunAsUserToken;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
/**
* Tests {@link AspectJMethodSecurityInterceptor}.
*
* @author Ben Alex
* @author Luke Taylor
* @author Rob Winch
*/
public class AspectJMethodSecurityInterceptorTests {
private TestingAuthenticationToken token;
private AspectJMethodSecurityInterceptor interceptor;
private @Mock AccessDecisionManager adm;
private @Mock MethodSecurityMetadataSource mds;
private @Mock AuthenticationManager authman;
private @Mock AspectJCallback aspectJCallback;
private ProceedingJoinPoint joinPoint;
// ~ Methods
// ========================================================================================================
@Before
public final void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
SecurityContextHolder.clearContext();
token = new TestingAuthenticationToken("Test", "Password");
interceptor = new AspectJMethodSecurityInterceptor();
interceptor.setAccessDecisionManager(adm);
interceptor.setAuthenticationManager(authman);
interceptor.setSecurityMetadataSource(mds);
// Set up joinpoint information for the countLength method on TargetObject
joinPoint = mock(ProceedingJoinPoint.class); // new MockJoinPoint(new
// TargetObject(), method);
Signature sig = mock(Signature.class);
when(sig.getDeclaringType()).thenReturn(TargetObject.class);
JoinPoint.StaticPart staticPart = mock(JoinPoint.StaticPart.class);
when(joinPoint.getSignature()).thenReturn(sig);
when(joinPoint.getStaticPart()).thenReturn(staticPart);
CodeSignature codeSig = mock(CodeSignature.class);
when(codeSig.getName()).thenReturn("countLength");
when(codeSig.getDeclaringType()).thenReturn(TargetObject.class);
when(codeSig.getParameterTypes()).thenReturn(new Class[] { String.class });
when(staticPart.getSignature()).thenReturn(codeSig);
when(mds.getAttributes(any(JoinPoint.class))).thenReturn(
SecurityConfig.createList("ROLE_USER"));
when(authman.authenticate(token)).thenReturn(token);
}
@After
public void clearContext() {
SecurityContextHolder.clearContext();
}
@Test
public void callbackIsInvokedWhenPermissionGranted() throws Throwable {
SecurityContextHolder.getContext().setAuthentication(token);
interceptor.invoke(joinPoint, aspectJCallback);
verify(aspectJCallback).proceedWithObject();
// Just try the other method too
interceptor.invoke(joinPoint);
}
@SuppressWarnings("unchecked")
@Test
public void callbackIsNotInvokedWhenPermissionDenied() throws Exception {
doThrow(new AccessDeniedException("denied")).when(adm).decide(
any(Authentication.class), any(), any(Collection.class));
SecurityContextHolder.getContext().setAuthentication(token);
try {
interceptor.invoke(joinPoint, aspectJCallback);
fail("Expected AccessDeniedException");
}
catch (AccessDeniedException expected) {
}
verify(aspectJCallback, never()).proceedWithObject();
}
@Test
public void adapterHoldsCorrectData() throws Exception {
TargetObject to = new TargetObject();
Method m = ClassUtils.getMethodIfAvailable(TargetObject.class, "countLength",
new Class[] { String.class });
when(joinPoint.getTarget()).thenReturn(to);
when(joinPoint.getArgs()).thenReturn(new Object[] { "Hi" });
MethodInvocationAdapter mia = new MethodInvocationAdapter(joinPoint);
assertThat(mia.getArguments()[0]).isEqualTo("Hi");
assertThat(mia.getStaticPart()).isEqualTo(m);
assertThat(mia.getMethod()).isEqualTo(m);
assertThat(mia.getThis()).isSameAs(to);
}
@Test
public void afterInvocationManagerIsNotInvokedIfExceptionIsRaised() throws Throwable {
token.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(token);
AfterInvocationManager aim = mock(AfterInvocationManager.class);
interceptor.setAfterInvocationManager(aim);
when(aspectJCallback.proceedWithObject()).thenThrow(new RuntimeException());
try {
interceptor.invoke(joinPoint, aspectJCallback);
fail("Expected exception");
}
catch (RuntimeException expected) {
}
verifyZeroInteractions(aim);
}
// SEC-1967
@Test
@SuppressWarnings("unchecked")
public void invokeWithAspectJCallbackRunAsReplacementCleansAfterException()
throws Exception {
SecurityContext ctx = SecurityContextHolder.getContext();
ctx.setAuthentication(token);
token.setAuthenticated(true);
final RunAsManager runAs = mock(RunAsManager.class);
final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds",
token.getAuthorities(), TestingAuthenticationToken.class);
interceptor.setRunAsManager(runAs);
when(runAs.buildRunAs(eq(token), any(MethodInvocation.class), any(List.class)))
.thenReturn(runAsToken);
when(aspectJCallback.proceedWithObject()).thenThrow(new RuntimeException());
try {
interceptor.invoke(joinPoint, aspectJCallback);
fail("Expected Exception");
}
catch (RuntimeException success) {
}
// Check we've changed back
assertThat(SecurityContextHolder.getContext()).isSameAs(ctx);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(token);
}
// SEC-1967
@Test
@SuppressWarnings("unchecked")
public void invokeRunAsReplacementCleansAfterException() throws Throwable {
SecurityContext ctx = SecurityContextHolder.getContext();
ctx.setAuthentication(token);
token.setAuthenticated(true);
final RunAsManager runAs = mock(RunAsManager.class);
final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds",
token.getAuthorities(), TestingAuthenticationToken.class);
interceptor.setRunAsManager(runAs);
when(runAs.buildRunAs(eq(token), any(MethodInvocation.class), any(List.class)))
.thenReturn(runAsToken);
when(joinPoint.proceed()).thenThrow(new RuntimeException());
try {
interceptor.invoke(joinPoint);
fail("Expected Exception");
}
catch (RuntimeException success) {
}
// Check we've changed back
assertThat(SecurityContextHolder.getContext()).isSameAs(ctx);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(token);
}
}