/*
* Copyright 2002-2016 the original author or authors.
*
* 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.expression.method;
import static org.assertj.core.api.Assertions.assertThat;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collection;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.expression.Expression;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.annotation.sec2150.MethodInvocationFactory;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
import org.springframework.test.util.ReflectionTestUtils;
/**
*
* @author Luke Taylor
* @since 3.0
*/
public class PrePostAnnotationSecurityMetadataSourceTests {
private PrePostAnnotationSecurityMetadataSource mds = new PrePostAnnotationSecurityMetadataSource(
new ExpressionBasedAnnotationAttributeFactory(
new DefaultMethodSecurityExpressionHandler()));
private MockMethodInvocation voidImpl1;
private MockMethodInvocation voidImpl2;
private MockMethodInvocation voidImpl3;
private MockMethodInvocation listImpl1;
private MockMethodInvocation notherListImpl1;
private MockMethodInvocation notherListImpl2;
private MockMethodInvocation annotatedAtClassLevel;
private MockMethodInvocation annotatedAtInterfaceLevel;
private MockMethodInvocation annotatedAtMethodLevel;
@Before
public void setUpData() throws Exception {
voidImpl1 = new MockMethodInvocation(new ReturnVoidImpl1(), ReturnVoid.class,
"doSomething", List.class);
voidImpl2 = new MockMethodInvocation(new ReturnVoidImpl2(), ReturnVoid.class,
"doSomething", List.class);
voidImpl3 = new MockMethodInvocation(new ReturnVoidImpl3(), ReturnVoid.class,
"doSomething", List.class);
listImpl1 = new MockMethodInvocation(new ReturnAListImpl1(), ReturnAList.class,
"doSomething", List.class);
notherListImpl1 = new MockMethodInvocation(new ReturnAnotherListImpl1(),
ReturnAnotherList.class, "doSomething", List.class);
notherListImpl2 = new MockMethodInvocation(new ReturnAnotherListImpl2(),
ReturnAnotherList.class, "doSomething", List.class);
annotatedAtClassLevel = new MockMethodInvocation(
new CustomAnnotationAtClassLevel(), ReturnVoid.class, "doSomething",
List.class);
annotatedAtInterfaceLevel = new MockMethodInvocation(
new CustomAnnotationAtInterfaceLevel(), ReturnVoid2.class, "doSomething",
List.class);
annotatedAtMethodLevel = new MockMethodInvocation(
new CustomAnnotationAtMethodLevel(), ReturnVoid.class, "doSomething",
List.class);
}
@Test
public void classLevelPreAnnotationIsPickedUpWhenNoMethodLevelExists()
throws Exception {
ConfigAttribute[] attrs = mds.getAttributes(voidImpl1).toArray(
new ConfigAttribute[0]);
assertThat(attrs.length).isEqualTo(1);
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
assertThat(pre.getAuthorizeExpression()).isNotNull();
assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("someExpression");
assertThat(pre.getFilterExpression()).isNull();
}
@Test
public void mixedClassAndMethodPreAnnotationsAreBothIncluded() {
ConfigAttribute[] attrs = mds.getAttributes(voidImpl2).toArray(
new ConfigAttribute[0]);
assertThat(attrs.length).isEqualTo(1);
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("someExpression");
assertThat(pre.getFilterExpression()).isNotNull();
assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("somePreFilterExpression");
}
@Test
public void methodWithPreFilterOnlyIsAllowed() {
ConfigAttribute[] attrs = mds.getAttributes(voidImpl3).toArray(
new ConfigAttribute[0]);
assertThat(attrs.length).isEqualTo(1);
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("permitAll");
assertThat(pre.getFilterExpression()).isNotNull();
assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("somePreFilterExpression");
}
@Test
public void methodWithPostFilterOnlyIsAllowed() {
ConfigAttribute[] attrs = mds.getAttributes(listImpl1).toArray(
new ConfigAttribute[0]);
assertThat(attrs.length).isEqualTo(2);
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
assertThat(attrs[1] instanceof PostInvocationExpressionAttribute).isTrue();
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
PostInvocationExpressionAttribute post = (PostInvocationExpressionAttribute) attrs[1];
assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("permitAll");
assertThat(post.getFilterExpression()).isNotNull();
assertThat(post.getFilterExpression().getExpressionString()).isEqualTo("somePostFilterExpression");
}
@Test
public void interfaceAttributesAreIncluded() {
ConfigAttribute[] attrs = mds.getAttributes(notherListImpl1).toArray(
new ConfigAttribute[0]);
assertThat(attrs.length).isEqualTo(1);
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
assertThat(pre.getFilterExpression()).isNotNull();
assertThat(pre.getAuthorizeExpression()).isNotNull();
assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("interfaceMethodAuthzExpression");
assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("interfacePreFilterExpression");
}
@Test
public void classAttributesTakesPrecedeceOverInterfaceAttributes() {
ConfigAttribute[] attrs = mds.getAttributes(notherListImpl2).toArray(
new ConfigAttribute[0]);
assertThat(attrs.length).isEqualTo(1);
assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue();
PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0];
assertThat(pre.getFilterExpression()).isNotNull();
assertThat(pre.getAuthorizeExpression()).isNotNull();
assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("interfaceMethodAuthzExpression");
assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("classMethodPreFilterExpression");
}
@Test
public void customAnnotationAtClassLevelIsDetected() throws Exception {
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtClassLevel).toArray(
new ConfigAttribute[0]);
assertThat(attrs.length).isEqualTo(1);
}
@Test
public void customAnnotationAtInterfaceLevelIsDetected() throws Exception {
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtInterfaceLevel).toArray(
new ConfigAttribute[0]);
assertThat(attrs.length).isEqualTo(1);
}
@Test
public void customAnnotationAtMethodLevelIsDetected() throws Exception {
ConfigAttribute[] attrs = mds.getAttributes(annotatedAtMethodLevel).toArray(
new ConfigAttribute[0]);
assertThat(attrs.length).isEqualTo(1);
}
@Test
public void proxyFactoryInterfaceAttributesFound() throws Exception {
MockMethodInvocation mi = MethodInvocationFactory.createSec2150MethodInvocation();
Collection<ConfigAttribute> attributes = mds.getAttributes(mi);
assertThat(attributes.size()).isEqualTo(1);
Expression expression = (Expression) ReflectionTestUtils.getField(attributes
.iterator().next(), "authorizeExpression");
assertThat(expression.getExpressionString()).isEqualTo("hasRole('ROLE_PERSON')");
}
// ~ Inner Classes
// ==================================================================================================
public static interface ReturnVoid {
public void doSomething(List<?> param);
}
public static interface ReturnAList {
public List<?> doSomething(List<?> param);
}
@PreAuthorize("interfaceAuthzExpression")
public static interface ReturnAnotherList {
@PreAuthorize("interfaceMethodAuthzExpression")
@PreFilter(filterTarget = "param", value = "interfacePreFilterExpression")
public List<?> doSomething(List<?> param);
}
@PreAuthorize("someExpression")
public static class ReturnVoidImpl1 implements ReturnVoid {
public void doSomething(List<?> param) {
}
}
@PreAuthorize("someExpression")
public static class ReturnVoidImpl2 implements ReturnVoid {
@PreFilter(filterTarget = "param", value = "somePreFilterExpression")
public void doSomething(List<?> param) {
}
}
public static class ReturnVoidImpl3 implements ReturnVoid {
@PreFilter(filterTarget = "param", value = "somePreFilterExpression")
public void doSomething(List<?> param) {
}
}
public static class ReturnAListImpl1 implements ReturnAList {
@PostFilter("somePostFilterExpression")
public List<?> doSomething(List<?> param) {
return param;
}
}
public static class ReturnAListImpl2 implements ReturnAList {
@PreAuthorize("someExpression")
@PreFilter(filterTarget = "param", value = "somePreFilterExpression")
@PostFilter("somePostFilterExpression")
@PostAuthorize("somePostAuthorizeExpression")
public List<?> doSomething(List<?> param) {
return param;
}
}
public static class ReturnAnotherListImpl1 implements ReturnAnotherList {
public List<?> doSomething(List<?> param) {
return param;
}
}
public static class ReturnAnotherListImpl2 implements ReturnAnotherList {
@PreFilter(filterTarget = "param", value = "classMethodPreFilterExpression")
public List<?> doSomething(List<?> param) {
return param;
}
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@PreAuthorize("customAnnotationExpression")
public @interface CustomAnnotation {
}
@CustomAnnotation
public static interface ReturnVoid2 {
public void doSomething(List<?> param);
}
@CustomAnnotation
public static class CustomAnnotationAtClassLevel implements ReturnVoid {
public void doSomething(List<?> param) {
}
}
public static class CustomAnnotationAtInterfaceLevel implements ReturnVoid2 {
public void doSomething(List<?> param) {
}
}
public static class CustomAnnotationAtMethodLevel implements ReturnVoid {
@CustomAnnotation
public void doSomething(List<?> param) {
}
}
}