1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Polish AuthorizationManager Method Security

- Removed consolidated pointcut advisor in favor of each interceptor
being an advisor. This allows Spring AOP to do more of the heavy
lifting of selecting the set of interceptors that applies
- Created new method context for after interceptors instead of
modifying existing one
- Added documentation
- Added XML support
- Added AuthorizationInterceptorsOrder to simplify interceptor
ordering
- Adjusted annotation lookup to comply with JSR-250 spec
- Adjusted annotation lookup to exhaustively search for duplicate
annotations
- Separated into three @Configuration classes, one for each set of
authorization annotations

Issue gh-9289
This commit is contained in:
Josh Cummings
2021-04-07 15:33:47 -06:00
parent 84e2e80915
commit 67e5c05a47
72 changed files with 4510 additions and 2342 deletions
@@ -1,123 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.NonNull;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An {@link AuthorizationManager} which can determine if an {@link Authentication} has
* access to the {@link MethodInvocation} by evaluating if the {@link Authentication}
* contains a specified authority from the JSR-250 security annotations.
*
* @author Evgeniy Cheban
* @since 5.5
*/
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
private static final Set<Class<? extends Annotation>> JSR250_ANNOTATIONS = new HashSet<>();
static {
JSR250_ANNOTATIONS.add(DenyAll.class);
JSR250_ANNOTATIONS.add(PermitAll.class);
JSR250_ANNOTATIONS.add(RolesAllowed.class);
}
private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry();
private String rolePrefix = "ROLE_";
/**
* Sets the role prefix. Defaults to "ROLE_".
* @param rolePrefix the role prefix to use
*/
public void setRolePrefix(String rolePrefix) {
Assert.notNull(rolePrefix, "rolePrefix cannot be null");
this.rolePrefix = rolePrefix;
}
/**
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
* by evaluating if the {@link Authentication} contains a specified authority from the
* JSR-250 security annotations.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* @return an {@link AuthorizationDecision} or null if the JSR-250 security
* annotations is not present
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication,
MethodAuthorizationContext methodAuthorizationContext) {
AuthorizationManager<MethodAuthorizationContext> delegate = this.registry
.getManager(methodAuthorizationContext);
return delegate.check(authentication, methodAuthorizationContext);
}
private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
@NonNull
@Override
AuthorizationManager<MethodAuthorizationContext> resolveManager(Method method, Class<?> targetClass) {
for (Annotation annotation : findJsr250Annotations(method, targetClass)) {
if (annotation instanceof DenyAll) {
return (a, o) -> new AuthorizationDecision(false);
}
if (annotation instanceof PermitAll) {
return (a, o) -> new AuthorizationDecision(true);
}
if (annotation instanceof RolesAllowed) {
RolesAllowed rolesAllowed = (RolesAllowed) annotation;
return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix,
rolesAllowed.value());
}
}
return NULL_MANAGER;
}
private Set<Annotation> findJsr250Annotations(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
Set<Annotation> annotations = findAnnotations(specificMethod);
return (annotations.isEmpty()) ? findAnnotations(specificMethod.getDeclaringClass()) : annotations;
}
private Set<Annotation> findAnnotations(AnnotatedElement annotatedElement) {
return AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement, JSR250_ANNOTATIONS);
}
}
}
@@ -1,82 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.aopalliance;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.lang.NonNull;
import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* Provides security interception of AOP Alliance based method invocations.
*
* @author Evgeniy Cheban
* @since 5.5
*/
public final class AuthorizationMethodInterceptor implements MethodInterceptor {
private final AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> beforeAdvice;
private final AuthorizationMethodAfterAdvice<MethodAuthorizationContext> afterAdvice;
/**
* Creates an instance.
* @param beforeAdvice the {@link AuthorizationMethodBeforeAdvice} to use
* @param afterAdvice the {@link AuthorizationMethodAfterAdvice} to use
*/
public AuthorizationMethodInterceptor(AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> beforeAdvice,
AuthorizationMethodAfterAdvice<MethodAuthorizationContext> afterAdvice) {
this.beforeAdvice = beforeAdvice;
this.afterAdvice = afterAdvice;
}
/**
* This method should be used to enforce security on a {@link MethodInvocation}.
* @param mi the method being invoked which requires a security decision
* @return the returned value from the {@link MethodInvocation}
*/
@Override
public Object invoke(@NonNull MethodInvocation mi) throws Throwable {
MethodAuthorizationContext methodAuthorizationContext = getMethodAuthorizationContext(mi);
this.beforeAdvice.before(this::getAuthentication, methodAuthorizationContext);
Object returnedObject = mi.proceed();
return this.afterAdvice.after(this::getAuthentication, methodAuthorizationContext, returnedObject);
}
private MethodAuthorizationContext getMethodAuthorizationContext(MethodInvocation mi) {
Object target = mi.getThis();
Class<?> targetClass = (target != null) ? AopUtils.getTargetClass(target) : null;
return new MethodAuthorizationContext(mi, targetClass);
}
private Authentication getAuthentication() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
}
}
@@ -1,73 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.method;
import java.util.function.Supplier;
import org.springframework.aop.MethodMatcher;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An {@link AuthorizationMethodAfterAdvice} which can determine if an
* {@link Authentication} has access to the {@link T} object using an
* {@link AuthorizationManager} if a {@link MethodMatcher} matches.
*
* @param <T> the type of object that the authorization check is being done one.
* @author Evgeniy Cheban
* @since 5.5
*/
public final class AuthorizationManagerMethodAfterAdvice<T> implements AuthorizationMethodAfterAdvice<T> {
private final MethodMatcher methodMatcher;
private final AuthorizationManager<T> authorizationManager;
/**
* Creates an instance.
* @param methodMatcher the {@link MethodMatcher} to use
* @param authorizationManager the {@link AuthorizationManager} to use
*/
public AuthorizationManagerMethodAfterAdvice(MethodMatcher methodMatcher,
AuthorizationManager<T> authorizationManager) {
Assert.notNull(methodMatcher, "methodMatcher cannot be null");
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.methodMatcher = methodMatcher;
this.authorizationManager = authorizationManager;
}
/**
* Determines if an {@link Authentication} has access to the {@link T} object using
* the {@link AuthorizationManager}.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param object the {@link T} object to check
* @throws AccessDeniedException if access is not granted
*/
@Override
public Object after(Supplier<Authentication> authentication, T object, Object returnedObject) {
this.authorizationManager.verify(authentication, object);
return returnedObject;
}
@Override
public MethodMatcher getMethodMatcher() {
return this.methodMatcher;
}
}
@@ -1,72 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.method;
import java.util.function.Supplier;
import org.springframework.aop.MethodMatcher;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An {@link AuthorizationMethodBeforeAdvice} which can determine if an
* {@link Authentication} has access to the {@link T} object using an
* {@link AuthorizationManager} if a {@link MethodMatcher} matches.
*
* @param <T> the type of object that the authorization check is being done one.
* @author Evgeniy Cheban
* @since 5.5
*/
public final class AuthorizationManagerMethodBeforeAdvice<T> implements AuthorizationMethodBeforeAdvice<T> {
private final MethodMatcher methodMatcher;
private final AuthorizationManager<T> authorizationManager;
/**
* Creates an instance.
* @param methodMatcher the {@link MethodMatcher} to use
* @param authorizationManager the {@link AuthorizationManager} to use
*/
public AuthorizationManagerMethodBeforeAdvice(MethodMatcher methodMatcher,
AuthorizationManager<T> authorizationManager) {
Assert.notNull(methodMatcher, "methodMatcher cannot be null");
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.methodMatcher = methodMatcher;
this.authorizationManager = authorizationManager;
}
/**
* Determines if an {@link Authentication} has access to the {@link T} object using
* the {@link AuthorizationManager}.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param object the {@link T} object to check
* @throws AccessDeniedException if access is not granted
*/
@Override
public void before(Supplier<Authentication> authentication, T object) {
this.authorizationManager.verify(authentication, object);
}
@Override
public MethodMatcher getMethodMatcher() {
return this.methodMatcher;
}
}
@@ -1,61 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.method;
import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.Pointcut;
import org.springframework.security.core.Authentication;
/**
* An Authorization advice that can determine if an {@link Authentication} has access to
* the returned object from the {@link MethodInvocation}. The {@link #getMethodMatcher()}
* describes when the advice applies for the method.
*
* @param <T> the type of object that the authorization check is being done one.
* @author Evgeniy Cheban
* @since 5.5
*/
public interface AuthorizationMethodAfterAdvice<T> extends Pointcut {
/**
* Returns the default {@link ClassFilter}.
* @return the {@link ClassFilter#TRUE} to use
*/
@Override
default ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
/**
* Determines if an {@link Authentication} has access to the returned object from the
* {@link MethodInvocation}.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param object the {@link T} object to check
* @param returnedObject the returned object from the {@link MethodInvocation} to
* check
* @return the <code>Object</code> that will ultimately be returned to the caller (if
* an implementation does not wish to modify the object to be returned to the caller,
* the implementation should simply return the same object it was passed by the
* <code>returnedObject</code> method argument)
*/
Object after(Supplier<Authentication> authentication, T object, Object returnedObject);
}
@@ -1,52 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.method;
import java.util.function.Supplier;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.Pointcut;
import org.springframework.security.core.Authentication;
/**
* An advice which can determine if an {@link Authentication} has access to the {@link T}
* object. The {@link #getMethodMatcher()} describes when the advice applies for the
* method.
*
* @param <T> the type of object that the authorization check is being done one.
* @author Evgeniy Cheban
* @since 5.5
*/
public interface AuthorizationMethodBeforeAdvice<T> extends Pointcut {
/**
* Returns the default {@link ClassFilter}.
* @return the {@link ClassFilter#TRUE} to use
*/
@Override
default ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
/**
* Determines if an {@link Authentication} has access to the {@link T} object.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param object the {@link T} object to check
*/
void before(Supplier<Authentication> authentication, T object);
}
@@ -1,102 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.method;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.support.StaticMethodMatcher;
import org.springframework.core.log.LogMessage;
import org.springframework.security.core.Authentication;
/**
* An {@link AuthorizationMethodAfterAdvice} which delegates to specific
* {@link AuthorizationMethodAfterAdvice}s and returns the result (possibly modified) from
* the {@link MethodInvocation}.
*
* @author Evgeniy Cheban
* @since 5.5
*/
public final class DelegatingAuthorizationMethodAfterAdvice
implements AuthorizationMethodAfterAdvice<MethodAuthorizationContext> {
private final Log logger = LogFactory.getLog(getClass());
private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
for (AuthorizationMethodAfterAdvice<MethodAuthorizationContext> delegate : DelegatingAuthorizationMethodAfterAdvice.this.delegates) {
MethodMatcher methodMatcher = delegate.getMethodMatcher();
if (methodMatcher.matches(method, targetClass)) {
return true;
}
}
return false;
}
};
private final List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates;
/**
* Creates an instance.
* @param delegates the {@link AuthorizationMethodAfterAdvice}s to use
*/
public DelegatingAuthorizationMethodAfterAdvice(
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates) {
this.delegates = delegates;
}
@Override
public MethodMatcher getMethodMatcher() {
return this.methodMatcher;
}
/**
* Delegates to specific {@link AuthorizationMethodAfterAdvice}s and returns the
* <code>returnedObject</code> (possibly modified) from the method argument.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* @param returnedObject the returned object from the {@link MethodInvocation} to
* check
* @return the <code>returnedObject</code> (possibly modified) from the method
* argument
*/
@Override
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext,
Object returnedObject) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(
LogMessage.format("Post Authorizing %s from %s", returnedObject, methodAuthorizationContext));
}
Object result = returnedObject;
for (AuthorizationMethodAfterAdvice<MethodAuthorizationContext> delegate : this.delegates) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Checking authorization on %s from %s using %s", result,
methodAuthorizationContext, delegate));
}
result = delegate.after(authentication, methodAuthorizationContext, result);
}
return result;
}
}
@@ -1,95 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.method;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.support.StaticMethodMatcher;
import org.springframework.core.log.LogMessage;
import org.springframework.security.core.Authentication;
/**
* An {@link AuthorizationMethodBeforeAdvice} which delegates to a specific
* {@link AuthorizationMethodBeforeAdvice} and grants access if all
* {@link AuthorizationMethodBeforeAdvice}s granted or abstained. Denies access only if
* one of the {@link AuthorizationMethodBeforeAdvice}s denied.
*
* @author Evgeniy Cheban
* @since 5.5
*/
public final class DelegatingAuthorizationMethodBeforeAdvice
implements AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> {
private final Log logger = LogFactory.getLog(getClass());
private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
for (AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> delegate : DelegatingAuthorizationMethodBeforeAdvice.this.delegates) {
MethodMatcher methodMatcher = delegate.getMethodMatcher();
if (methodMatcher.matches(method, targetClass)) {
return true;
}
}
return false;
}
};
private final List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates;
/**
* Creates an instance.
* @param delegates the {@link AuthorizationMethodBeforeAdvice}s to use
*/
public DelegatingAuthorizationMethodBeforeAdvice(
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates) {
this.delegates = delegates;
}
@Override
public MethodMatcher getMethodMatcher() {
return this.methodMatcher;
}
/**
* Delegates to a specific {@link AuthorizationMethodBeforeAdvice} and grants access
* if all {@link AuthorizationMethodBeforeAdvice}s granted or abstained. Denies only
* if one of the {@link AuthorizationMethodBeforeAdvice}s denied.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
*/
@Override
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Pre Authorizing %s", methodAuthorizationContext));
}
for (AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> delegate : this.delegates) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", methodAuthorizationContext,
delegate));
}
delegate.before(authentication, methodAuthorizationContext);
}
}
}
@@ -1,84 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.method;
import org.aopalliance.intercept.MethodInvocation;
/**
* An authorization context which is holds the {@link MethodInvocation}, the target class
* and the returned object.
*
* @author Evgeniy Cheban
* @since 5.5
*/
public final class MethodAuthorizationContext {
private final MethodInvocation methodInvocation;
private final Class<?> targetClass;
private Object returnObject;
/**
* Creates an instance.
* @param methodInvocation the {@link MethodInvocation} to use
* @param targetClass the target class to use
*/
public MethodAuthorizationContext(MethodInvocation methodInvocation, Class<?> targetClass) {
this.methodInvocation = methodInvocation;
this.targetClass = targetClass;
}
/**
* Returns the {@link MethodInvocation}.
* @return the {@link MethodInvocation} to use
*/
public MethodInvocation getMethodInvocation() {
return this.methodInvocation;
}
/**
* Returns the target class.
* @return the target class to use
*/
public Class<?> getTargetClass() {
return this.targetClass;
}
/**
* Returns the returned object from the {@link MethodInvocation}.
* @return the returned object from the {@link MethodInvocation} to use
*/
public Object getReturnObject() {
return this.returnObject;
}
/**
* Sets the returned object from the {@link MethodInvocation}.
* @param returnObject the returned object from the {@link MethodInvocation} to use
*/
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
@Override
public String toString() {
return "MethodAuthorizationContext[methodInvocation=" + this.methodInvocation + ", targetClass="
+ this.targetClass + ", returnObject=" + this.returnObject + ']';
}
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.security.access.annotation;
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import java.util.Map;
@@ -22,34 +22,31 @@ import java.util.concurrent.ConcurrentHashMap;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.NonNull;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.authorization.AuthorizationManager;
/**
* An abstract registry which provides an {@link AuthorizationManager} for the
* {@link MethodInvocation}.
* For internal use only, as this contract is likely to change
*
* @author Evgeniy Cheban
* @since 5.5
*/
abstract class AbstractAuthorizationManagerRegistry {
static final AuthorizationManager<MethodAuthorizationContext> NULL_MANAGER = (a, o) -> null;
static final AuthorizationManager<MethodInvocation> NULL_MANAGER = (a, o) -> null;
private final Map<MethodClassKey, AuthorizationManager<MethodAuthorizationContext>> cachedManagers = new ConcurrentHashMap<>();
private final Map<MethodClassKey, AuthorizationManager<MethodInvocation>> cachedManagers = new ConcurrentHashMap<>();
/**
* Returns an {@link AuthorizationManager} for the {@link MethodAuthorizationContext}.
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to use
* Returns an {@link AuthorizationManager} for the {@link MethodInvocation}.
* @param methodInvocation the {@link MethodInvocation} to use
* @return an {@link AuthorizationManager} to use
*/
final AuthorizationManager<MethodAuthorizationContext> getManager(
MethodAuthorizationContext methodAuthorizationContext) {
MethodInvocation methodInvocation = methodAuthorizationContext.getMethodInvocation();
final AuthorizationManager<MethodInvocation> getManager(MethodInvocation methodInvocation) {
Method method = methodInvocation.getMethod();
Class<?> targetClass = methodAuthorizationContext.getTargetClass();
Object target = methodInvocation.getThis();
Class<?> targetClass = (target != null) ? AopUtils.getTargetClass(target) : null;
MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
return this.cachedManagers.computeIfAbsent(cacheKey, (k) -> resolveManager(method, targetClass));
}
@@ -62,6 +59,6 @@ abstract class AbstractAuthorizationManagerRegistry {
* @return the non-null {@link AuthorizationManager}
*/
@NonNull
abstract AuthorizationManager<MethodAuthorizationContext> resolveManager(Method method, Class<?> targetClass);
abstract AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass);
}
@@ -22,30 +22,28 @@ import java.util.concurrent.ConcurrentHashMap;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.NonNull;
import org.springframework.security.access.method.MethodAuthorizationContext;
/**
* An abstract registry which provides an {@link ExpressionAttribute} for the
* {@link MethodInvocation}.
* For internal use only, as this contract is likely to change
*
* @author Evgeniy Cheban
* @since 5.5
*/
abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute> {
private final Map<MethodClassKey, T> cachedAttributes = new ConcurrentHashMap<>();
/**
* Returns an {@link ExpressionAttribute} for the {@link MethodAuthorizationContext}.
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to use
* Returns an {@link ExpressionAttribute} for the {@link MethodInvocation}.
* @param mi the {@link MethodInvocation} to use
* @return the {@link ExpressionAttribute} to use
*/
final T getAttribute(MethodAuthorizationContext methodAuthorizationContext) {
MethodInvocation methodInvocation = methodAuthorizationContext.getMethodInvocation();
Method method = methodInvocation.getMethod();
Class<?> targetClass = methodAuthorizationContext.getTargetClass();
final T getAttribute(MethodInvocation mi) {
Method method = mi.getMethod();
Object target = mi.getThis();
Class<?> targetClass = (target != null) ? AopUtils.getTargetClass(target) : null;
return getAttribute(method, targetClass);
}
@@ -0,0 +1,113 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.RepeatableContainers;
/**
* A wrapper around {@link AnnotationUtils} that checks for, and errors on, conflicting
* annotations. This is specifically important for Spring Security annotations which are
* not designed to be repeatable.
*
* There are numerous ways that two annotations of the same type may be attached to the
* same method. For example, a class may implement a method defined in two separate
* interfaces. If both of those interfaces have a `@PreAuthorize` annotation, then it's
* unclear which `@PreAuthorize` expression Spring Security should use.
*
* Another way is when one of Spring Security's annotations is used as a meta-annotation.
* In that case, two custom annotations can be declared, each with their own
* `@PreAuthorize` declaration. If both custom annotations are used on the same method,
* then it's unclear which `@PreAuthorize` expression Spring Security should use.
*
* @author Josh Cummings
*/
final class AuthorizationAnnotationUtils {
/**
* Perform an exhaustive search on the type hierarchy of the given {@link Method} for
* the annotation of type {@code annotationType}, including any annotations using
* {@code annotationType} as a meta-annotation.
*
* If more than one is found, then throw an error.
* @param method the method declaration to search from
* @param annotationType the annotation type to search for
* @return the unique instance of the annotation attributed to the method,
* {@code null} otherwise
* @throws AnnotationConfigurationException if more than one instance of the
* annotation is found
*/
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType) {
MergedAnnotations mergedAnnotations = MergedAnnotations.from(method,
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
if (hasDuplicate(mergedAnnotations, annotationType)) {
throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType
+ " attributed to " + method
+ " Please remove the duplicate annotations and publish a bean to handle your authorization logic.");
}
return AnnotationUtils.findAnnotation(method, annotationType);
}
/**
* Perform an exhaustive search on the type hierarchy of the given {@link Class} for
* the annotation of type {@code annotationType}, including any annotations using
* {@code annotationType} as a meta-annotation.
*
* If more than one is found, then throw an error.
* @param type the type to search from
* @param annotationType the annotation type to search for
* @return the unique instance of the annotation attributed to the method,
* {@code null} otherwise
* @throws AnnotationConfigurationException if more than one instance of the
* annotation is found
*/
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType) {
MergedAnnotations mergedAnnotations = MergedAnnotations.from(type,
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
if (hasDuplicate(mergedAnnotations, annotationType)) {
throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType
+ " attributed to " + type
+ " Please remove the duplicate annotations and publish a bean to handle your authorization logic.");
}
return AnnotationUtils.findAnnotation(type, annotationType);
}
private static <A extends Annotation> boolean hasDuplicate(MergedAnnotations mergedAnnotations,
Class<A> annotationType) {
boolean alreadyFound = false;
for (MergedAnnotation<Annotation> mergedAnnotation : mergedAnnotations) {
if (mergedAnnotation.getType() == annotationType) {
if (alreadyFound) {
return true;
}
alreadyFound = true;
}
}
return false;
}
private AuthorizationAnnotationUtils() {
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import org.springframework.aop.Advisor;
/**
* Ordering of Spring Security's authorization {@link Advisor}s
*
* @author Josh Cummings
* @since 5.6
* @see PreAuthorizeAuthorizationManager
* @see PostAuthorizeAuthorizationManager
* @see SecuredAuthorizationManager
* @see Jsr250AuthorizationManager
*/
public enum AuthorizationInterceptorsOrder {
FIRST(Integer.MIN_VALUE),
/**
* {@link PreFilterAuthorizationMethodInterceptor}
*/
PRE_FILTER,
PRE_AUTHORIZE,
SECURED,
JSR250,
POST_AUTHORIZE,
/**
* {@link PostFilterAuthorizationMethodInterceptor}
*/
POST_FILTER,
LAST(Integer.MAX_VALUE);
private static final int INTERVAL = 100;
private final int order;
AuthorizationInterceptorsOrder() {
this.order = ordinal() * INTERVAL;
}
AuthorizationInterceptorsOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
}
@@ -0,0 +1,137 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.util.function.Supplier;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
/**
* A {@link MethodInterceptor} which can determine if an {@link Authentication} has access
* to the result of an {@link MethodInvocation} using an {@link AuthorizationManager}
*
* @author Evgeniy Cheban
* @author Josh Cummings
* @since 5.6
*/
public final class AuthorizationManagerAfterMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
};
private final Pointcut pointcut;
private final AuthorizationManager<MethodInvocationResult> authorizationManager;
private int order;
/**
* Creates an instance.
* @param pointcut the {@link Pointcut} to use
* @param authorizationManager the {@link AuthorizationManager} to use
*/
public AuthorizationManagerAfterMethodInterceptor(Pointcut pointcut,
AuthorizationManager<MethodInvocationResult> authorizationManager) {
Assert.notNull(pointcut, "pointcut cannot be null");
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.pointcut = pointcut;
this.authorizationManager = authorizationManager;
}
/**
* Creates an interceptor for the {@link PostAuthorize} annotation
* @return the interceptor
*/
public static AuthorizationManagerAfterMethodInterceptor postAuthorize() {
return postAuthorize(new PostAuthorizeAuthorizationManager());
}
/**
* Creates an interceptor for the {@link PostAuthorize} annotation
* @param authorizationManager the {@link PostAuthorizeAuthorizationManager} to use
* @return the interceptor
*/
public static AuthorizationManagerAfterMethodInterceptor postAuthorize(
PostAuthorizeAuthorizationManager authorizationManager) {
AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(PostAuthorize.class), authorizationManager);
interceptor.setOrder(500);
return interceptor;
}
/**
* Determine if an {@link Authentication} has access to the {@link MethodInvocation}
* using the {@link AuthorizationManager}.
* @param mi the {@link MethodInvocation} to check
* @throws AccessDeniedException if access is not granted
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object result = mi.proceed();
this.authorizationManager.verify(AUTHENTICATION_SUPPLIER, new MethodInvocationResult(mi, result));
return result;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
/**
* {@inheritDoc}
*/
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
}
@@ -0,0 +1,183 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.util.function.Supplier;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.core.Ordered;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
/**
* A {@link MethodInterceptor} which uses a {@link AuthorizationManager} to determine if
* an {@link Authentication} may invoke the given {@link MethodInvocation}
*
* @author Evgeniy Cheban
* @author Josh Cummings
* @since 5.6
*/
public final class AuthorizationManagerBeforeMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
};
private final Pointcut pointcut;
private final AuthorizationManager<MethodInvocation> authorizationManager;
private int order = AuthorizationInterceptorsOrder.FIRST.getOrder();
/**
* Creates an instance.
* @param pointcut the {@link Pointcut} to use
* @param authorizationManager the {@link AuthorizationManager} to use
*/
public AuthorizationManagerBeforeMethodInterceptor(Pointcut pointcut,
AuthorizationManager<MethodInvocation> authorizationManager) {
Assert.notNull(pointcut, "pointcut cannot be null");
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.pointcut = pointcut;
this.authorizationManager = authorizationManager;
}
/**
* Creates an interceptor for the {@link PreAuthorize} annotation
* @return the interceptor
*/
public static AuthorizationManagerBeforeMethodInterceptor preAuthorize() {
return preAuthorize(new PreAuthorizeAuthorizationManager());
}
/**
* Creates an interceptor for the {@link PreAuthorize} annotation
* @param authorizationManager the {@link PreAuthorizeAuthorizationManager} to use
* @return the interceptor
*/
public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(
PreAuthorizeAuthorizationManager authorizationManager) {
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder());
return interceptor;
}
/**
* Creates an interceptor for the {@link Secured} annotation
* @return the interceptor
*/
public static AuthorizationManagerBeforeMethodInterceptor secured() {
return secured(new SecuredAuthorizationManager());
}
/**
* Creates an interceptor for the {@link Secured} annotation
* @param authorizationManager the {@link SecuredAuthorizationManager} to use
* @return the interceptor
*/
public static AuthorizationManagerBeforeMethodInterceptor secured(
SecuredAuthorizationManager authorizationManager) {
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(Secured.class), authorizationManager);
interceptor.setOrder(AuthorizationInterceptorsOrder.SECURED.getOrder());
return interceptor;
}
/**
* Creates an interceptor for the JSR-250 annotations
* @return the interceptor
*/
public static AuthorizationManagerBeforeMethodInterceptor jsr250() {
return jsr250(new Jsr250AuthorizationManager());
}
/**
* Creates an interceptor for the JSR-250 annotations
* @param authorizationManager the {@link Jsr250AuthorizationManager} to use
* @return the interceptor
*/
public static AuthorizationManagerBeforeMethodInterceptor jsr250(Jsr250AuthorizationManager authorizationManager) {
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(RolesAllowed.class, DenyAll.class, PermitAll.class),
authorizationManager);
interceptor.setOrder(AuthorizationInterceptorsOrder.JSR250.getOrder());
return interceptor;
}
/**
* Determine if an {@link Authentication} has access to the {@link MethodInvocation}
* using the configured {@link AuthorizationManager}.
* @param mi the {@link MethodInvocation} to check
* @throws AccessDeniedException if access is not granted
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.authorizationManager.verify(AUTHENTICATION_SUPPLIER, mi);
return mi.proceed();
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
/**
* {@inheritDoc}
*/
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.lang.annotation.Annotation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.Pointcuts;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
/**
* @author Josh Cummings
*/
final class AuthorizationMethodPointcuts {
@SafeVarargs
static Pointcut forAnnotations(Class<? extends Annotation>... annotations) {
ComposablePointcut pointcut = null;
for (Class<? extends Annotation> annotation : annotations) {
if (pointcut == null) {
pointcut = new ComposablePointcut(classOrMethod(annotation));
}
else {
pointcut.union(classOrMethod(annotation));
}
}
return pointcut;
}
private static Pointcut classOrMethod(Class<? extends Annotation> annotation) {
return Pointcuts.union(new AnnotationMatchingPointcut(null, annotation, true),
new AnnotationMatchingPointcut(annotation, true));
}
private AuthorizationMethodPointcuts() {
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.lang.NonNull;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
* invoke the {@link MethodInvocation} by evaluating if the {@link Authentication}
* contains a specified authority from the JSR-250 security annotations.
*
* @author Evgeniy Cheban
* @author Josh Cummings
* @since 5.6
*/
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {
private static final Set<Class<? extends Annotation>> JSR250_ANNOTATIONS = new HashSet<>();
static {
JSR250_ANNOTATIONS.add(DenyAll.class);
JSR250_ANNOTATIONS.add(PermitAll.class);
JSR250_ANNOTATIONS.add(RolesAllowed.class);
}
private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry();
private String rolePrefix = "ROLE_";
/**
* Sets the role prefix. Defaults to "ROLE_".
* @param rolePrefix the role prefix to use
*/
public void setRolePrefix(String rolePrefix) {
Assert.notNull(rolePrefix, "rolePrefix cannot be null");
this.rolePrefix = rolePrefix;
}
/**
* Determine if an {@link Authentication} has access to a method by evaluating the
* {@link DenyAll}, {@link PermitAll}, and {@link RolesAllowed} annotations that
* {@link MethodInvocation} specifies.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodInvocation the {@link MethodInvocation} to check
* @return an {@link AuthorizationDecision} or null if the JSR-250 security
* annotations is not present
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {
AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);
return delegate.check(authentication, methodInvocation);
}
private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
@NonNull
@Override
AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
Annotation annotation = findJsr250Annotation(method, targetClass);
if (annotation instanceof DenyAll) {
return (a, o) -> new AuthorizationDecision(false);
}
if (annotation instanceof PermitAll) {
return (a, o) -> new AuthorizationDecision(true);
}
if (annotation instanceof RolesAllowed) {
RolesAllowed rolesAllowed = (RolesAllowed) annotation;
return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix,
rolesAllowed.value());
}
return NULL_MANAGER;
}
private Annotation findJsr250Annotation(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
Annotation annotation = findAnnotation(specificMethod);
return (annotation != null) ? annotation : findAnnotation(specificMethod.getDeclaringClass());
}
private Annotation findAnnotation(Method method) {
Set<Annotation> annotations = new HashSet<>();
for (Class<? extends Annotation> annotationClass : JSR250_ANNOTATIONS) {
Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(method, annotationClass);
if (annotation != null) {
annotations.add(annotation);
}
}
if (annotations.isEmpty()) {
return null;
}
if (annotations.size() > 1) {
throw new AnnotationConfigurationException(
"The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same method.");
}
return annotations.iterator().next();
}
private Annotation findAnnotation(Class<?> clazz) {
Set<Annotation> annotations = new HashSet<>();
for (Class<? extends Annotation> annotationClass : JSR250_ANNOTATIONS) {
Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, annotationClass);
if (annotation != null) {
annotations.add(annotation);
}
}
if (annotations.isEmpty()) {
return null;
}
if (annotations.size() > 1) {
throw new AnnotationConfigurationException(
"The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same class definition.");
}
return annotations.iterator().next();
}
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.Assert;
/**
* A context object that contains a {@link MethodInvocation} and the result of that
* {@link MethodInvocation}.
*
* @author Josh Cummings
* @since 5.6
*/
public class MethodInvocationResult {
private final MethodInvocation methodInvocation;
private final Object result;
/**
* Construct a {@link MethodInvocationResult} with the provided parameters
* @param methodInvocation the already-invoked {@link MethodInvocation}
* @param result the value returned from the {@link MethodInvocation}
*/
public MethodInvocationResult(MethodInvocation methodInvocation, Object result) {
Assert.notNull(methodInvocation, "methodInvocation cannot be null");
this.methodInvocation = methodInvocation;
this.result = result;
}
/**
* Return the already-invoked {@link MethodInvocation}
* @return the already-invoked {@link MethodInvocation}
*/
public MethodInvocation getMethodInvocation() {
return this.methodInvocation;
}
/**
* Return the result of the already-invoked {@link MethodInvocation}
* @return the result
*/
public Object getResult() {
return this.result;
}
}
@@ -23,13 +23,11 @@ import org.aopalliance.intercept.MethodInvocation;
import reactor.util.annotation.NonNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
@@ -37,21 +35,21 @@ import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An {@link AuthorizationManager} which can determine if an {@link Authentication} has
* access to the {@link MethodInvocation} by evaluating an expression from the
* {@link PostAuthorize} annotation.
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
* return the result from an invoked {@link MethodInvocation} by evaluating an expression
* from the {@link PostAuthorize} annotation.
*
* @author Evgeniy Cheban
* @since 5.5
* @since 5.6
*/
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
/**
* Sets the {@link MethodSecurityExpressionHandler}.
* Use this the {@link MethodSecurityExpressionHandler}.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
@@ -60,23 +58,23 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
}
/**
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
* by evaluating an expression from the {@link PostAuthorize} annotation.
* Determine if an {@link Authentication} has access to the returned object by
* evaluating the {@link PostAuthorize} annotation that the {@link MethodInvocation}
* specifies.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* @return an {@link AuthorizationDecision} or null if the {@link PostAuthorize}
* annotation is not present
* @param mi the {@link MethodInvocationResult} to check
* @return an {@link AuthorizationDecision} or {@code null} if the
* {@link PostAuthorize} annotation is not present
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication,
MethodAuthorizationContext methodAuthorizationContext) {
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult mi) {
ExpressionAttribute attribute = this.registry.getAttribute(mi.getMethodInvocation());
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return null;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
methodAuthorizationContext.getMethodInvocation());
this.expressionHandler.setReturnObject(methodAuthorizationContext.getReturnObject(), ctx);
mi.getMethodInvocation());
this.expressionHandler.setReturnObject(mi.getResult(), ctx);
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
return new AuthorizationDecision(granted);
}
@@ -98,9 +96,10 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
}
private PostAuthorize findPostAuthorizeAnnotation(Method method) {
PostAuthorize postAuthorize = AnnotationUtils.findAnnotation(method, PostAuthorize.class);
return (postAuthorize != null) ? postAuthorize
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostAuthorize.class);
PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method,
PostAuthorize.class);
return (postAuthorize != null) ? postAuthorize : AuthorizationAnnotationUtils
.findUniqueAnnotation(method.getDeclaringClass(), PostAuthorize.class);
}
}
@@ -19,48 +19,65 @@ package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.StaticMethodMatcher;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
/**
* An {@link AuthorizationMethodAfterAdvice} which filters a <code>returnedObject</code>
* from the {@link MethodInvocation} by evaluating an expression from the
* {@link PostFilter} annotation.
* A {@link MethodInterceptor} which filters a {@code returnedObject} from the
* {@link MethodInvocation} by evaluating an expression from the {@link PostFilter}
* annotation.
*
* @author Evgeniy Cheban
* @since 5.5
* @author Josh Cummings
* @since 5.6
*/
public final class PostFilterAuthorizationMethodAfterAdvice
implements AuthorizationMethodAfterAdvice<MethodAuthorizationContext> {
public final class PostFilterAuthorizationMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
};
private final PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry();
private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return PostFilterAuthorizationMethodAfterAdvice.this.registry.getAttribute(method,
targetClass) != ExpressionAttribute.NULL_ATTRIBUTE;
}
};
private int order = AuthorizationInterceptorsOrder.POST_FILTER.getOrder();
private final Pointcut pointcut;
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
/**
* Sets the {@link MethodSecurityExpressionHandler}.
* Creates a {@link PostFilterAuthorizationMethodInterceptor} using the provided
* parameters
*/
public PostFilterAuthorizationMethodInterceptor() {
this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class);
}
/**
* Use this {@link MethodSecurityExpressionHandler}.
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
@@ -68,35 +85,51 @@ public final class PostFilterAuthorizationMethodAfterAdvice
this.expressionHandler = expressionHandler;
}
/**
* {@inheritDoc}
*/
@Override
public MethodMatcher getMethodMatcher() {
return this.methodMatcher;
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
/**
* Filters a <code>returnedObject</code> from the {@link MethodInvocation} by
* evaluating an expression from the {@link PostFilter} annotation.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* @param returnedObject the returned object from the {@link MethodInvocation} to
* check
* @return filtered <code>returnedObject</code> from the {@link MethodInvocation}
* {@inheritDoc}
*/
@Override
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext,
Object returnedObject) {
if (returnedObject == null) {
return null;
}
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
/**
* Filter a {@code returnedObject} using the {@link PostFilter} annotation that the
* {@link MethodInvocation} specifies.
* @param mi the {@link MethodInvocation} to check check
* @return filtered {@code returnedObject}
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object returnedObject = mi.proceed();
ExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return returnedObject;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
methodAuthorizationContext.getMethodInvocation());
Object result = this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
methodAuthorizationContext.setReturnObject(result);
return result;
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER.get(), mi);
return this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
}
private final class PostFilterExpressionAttributeRegistry
@@ -110,15 +143,15 @@ public final class PostFilterAuthorizationMethodAfterAdvice
if (postFilter == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression postFilterExpression = PostFilterAuthorizationMethodAfterAdvice.this.expressionHandler
Expression postFilterExpression = PostFilterAuthorizationMethodInterceptor.this.expressionHandler
.getExpressionParser().parseExpression(postFilter.value());
return new ExpressionAttribute(postFilterExpression);
}
private PostFilter findPostFilterAnnotation(Method method) {
PostFilter postFilter = AnnotationUtils.findAnnotation(method, PostFilter.class);
PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class);
return (postFilter != null) ? postFilter
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostFilter.class);
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostFilter.class);
}
}
@@ -23,13 +23,11 @@ import org.aopalliance.intercept.MethodInvocation;
import reactor.util.annotation.NonNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
@@ -37,14 +35,14 @@ import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An {@link AuthorizationManager} which can determine if an {@link Authentication} has
* access to the {@link MethodInvocation} by evaluating an expression from the
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
* invoke the {@link MethodInvocation} by evaluating an expression from the
* {@link PreAuthorize} annotation.
*
* @author Evgeniy Cheban
* @since 5.5
* @since 5.6
*/
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
@@ -60,22 +58,21 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
}
/**
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
* by evaluating an expression from the {@link PreAuthorize} annotation.
* Determine if an {@link Authentication} has access to a method by evaluating an
* expression from the {@link PreAuthorize} annotation that the
* {@link MethodInvocation} specifies.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* @return an {@link AuthorizationDecision} or null if the {@link PreAuthorize}
* annotation is not present
* @param mi the {@link MethodInvocation} to check
* @return an {@link AuthorizationDecision} or {@code null} if the
* {@link PreAuthorize} annotation is not present
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication,
MethodAuthorizationContext methodAuthorizationContext) {
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
ExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return null;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
methodAuthorizationContext.getMethodInvocation());
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi);
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
return new AuthorizationDecision(granted);
}
@@ -97,9 +94,9 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
}
private PreAuthorize findPreAuthorizeAnnotation(Method method) {
PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(method, PreAuthorize.class);
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
return (preAuthorize != null) ? preAuthorize
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreAuthorize.class);
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreAuthorize.class);
}
}
@@ -19,48 +19,65 @@ package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.StaticMethodMatcher;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.Ordered;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An {@link AuthorizationMethodBeforeAdvice} which filters a method argument by
* evaluating an expression from the {@link PreFilter} annotation.
* A {@link MethodInterceptor} which filters a method argument by evaluating an expression
* from the {@link PreFilter} annotation.
*
* @author Evgeniy Cheban
* @since 5.5
* @author Josh Cummings
* @since 5.6
*/
public final class PreFilterAuthorizationMethodBeforeAdvice
implements AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> {
public final class PreFilterAuthorizationMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
private static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
};
private final PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry();
private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return PreFilterAuthorizationMethodBeforeAdvice.this.registry.getAttribute(method,
targetClass) != PreFilterExpressionAttribute.NULL_ATTRIBUTE;
}
};
private int order = AuthorizationInterceptorsOrder.PRE_FILTER.getOrder();
private final Pointcut pointcut;
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
/**
* Sets the {@link MethodSecurityExpressionHandler}.
* Creates a {@link PreFilterAuthorizationMethodInterceptor} using the provided
* parameters
*/
public PreFilterAuthorizationMethodInterceptor() {
this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class);
}
/**
* Use this {@link MethodSecurityExpressionHandler}
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
*/
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
@@ -68,27 +85,51 @@ public final class PreFilterAuthorizationMethodBeforeAdvice
this.expressionHandler = expressionHandler;
}
/**
* {@inheritDoc}
*/
@Override
public MethodMatcher getMethodMatcher() {
return this.methodMatcher;
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
/**
* Filters a method argument by evaluating an expression from the {@link PreFilter}
* annotation.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* {@inheritDoc}
*/
@Override
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext) {
PreFilterExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public boolean isPerInstance() {
return true;
}
/**
* Filter the method argument specified in the {@link PreFilter} annotation that
* {@link MethodInvocation} specifies.
* @param mi the {@link MethodInvocation} to check
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
return;
return mi.proceed();
}
MethodInvocation mi = methodAuthorizationContext.getMethodInvocation();
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi);
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER.get(), mi);
Object filterTarget = findFilterTarget(attribute.filterTarget, ctx, mi);
this.expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
return mi.proceed();
}
private Object findFilterTarget(String filterTargetName, EvaluationContext ctx, MethodInvocation methodInvocation) {
@@ -122,15 +163,15 @@ public final class PreFilterAuthorizationMethodBeforeAdvice
if (preFilter == null) {
return PreFilterExpressionAttribute.NULL_ATTRIBUTE;
}
Expression preFilterExpression = PreFilterAuthorizationMethodBeforeAdvice.this.expressionHandler
Expression preFilterExpression = PreFilterAuthorizationMethodInterceptor.this.expressionHandler
.getExpressionParser().parseExpression(preFilter.value());
return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget());
}
private PreFilter findPreFilterAnnotation(Method method) {
PreFilter preFilter = AnnotationUtils.findAnnotation(method, PreFilter.class);
PreFilter preFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreFilter.class);
return (preFilter != null) ? preFilter
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreFilter.class);
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreFilter.class);
}
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.security.access.annotation;
package org.springframework.security.authorization.method;
import java.lang.reflect.Method;
import java.util.function.Supplier;
@@ -22,57 +22,53 @@ import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
/**
* An {@link AuthorizationManager} which can determine if an {@link Authentication} has
* access to the {@link MethodInvocation} by evaluating if the {@link Authentication}
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
* invoke the {@link MethodInvocation} by evaluating if the {@link Authentication}
* contains a specified authority from the Spring Security's {@link Secured} annotation.
*
* @author Evgeniy Cheban
* @since 5.5
* @since 5.6
*/
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
private final SecuredAuthorizationManagerRegistry registry = new SecuredAuthorizationManagerRegistry();
/**
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
* by evaluating if the {@link Authentication} contains a specified authority from the
* Spring Security's {@link Secured} annotation.
* Determine if an {@link Authentication} has access to a method by evaluating the
* {@link Secured} annotation that {@link MethodInvocation} specifies.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* @param mi the {@link MethodInvocation} to check
* @return an {@link AuthorizationDecision} or null if the {@link Secured} annotation
* is not present
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication,
MethodAuthorizationContext methodAuthorizationContext) {
AuthorizationManager<MethodAuthorizationContext> delegate = this.registry
.getManager(methodAuthorizationContext);
return delegate.check(authentication, methodAuthorizationContext);
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(mi);
return delegate.check(authentication, mi);
}
private static final class SecuredAuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
@NonNull
@Override
AuthorizationManager<MethodAuthorizationContext> resolveManager(Method method, Class<?> targetClass) {
AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
Secured secured = findSecuredAnnotation(specificMethod);
return (secured != null) ? AuthorityAuthorizationManager.hasAnyAuthority(secured.value()) : NULL_MANAGER;
}
private Secured findSecuredAnnotation(Method method) {
Secured secured = AnnotationUtils.findAnnotation(method, Secured.class);
Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class);
return (secured != null) ? secured
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), Secured.class);
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), Secured.class);
}
}
@@ -60,4 +60,10 @@ public interface BusinessService extends Serializable {
List<?> methodReturningAList(String userName, String extraParam);
@RequireAdminRole
@RequireUserRole
default void repeatedAnnotations() {
}
}
@@ -1,167 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.annotation;
import java.util.function.Supplier;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import org.junit.Test;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link Jsr250AuthorizationManager}.
*
* @author Evgeniy Cheban
*/
public class Jsr250AuthorizationManagerTests {
@Test
public void rolePrefixWhenNotSetThenDefaultsToRole() {
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
assertThat(manager).extracting("rolePrefix").isEqualTo("ROLE_");
}
@Test
public void setRolePrefixWhenNullThenException() {
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
assertThatIllegalArgumentException().isThrownBy(() -> manager.setRolePrefix(null))
.withMessage("rolePrefix cannot be null");
}
@Test
public void setRolePrefixWhenNotNullThenSets() {
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
manager.setRolePrefix("CUSTOM_");
assertThat(manager).extracting("rolePrefix").isEqualTo("CUSTOM_");
}
@Test
public void checkDoSomethingWhenNoJsr250AnnotationsThenNullDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
assertThat(decision).isNull();
}
@Test
public void checkPermitAllRolesAllowedAdminWhenRoleUserThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"permitAllRolesAllowedAdmin");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkDenyAllRolesAllowedAdminWhenRoleAdminThenDeniedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"denyAllRolesAllowedAdmin");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
methodAuthorizationContext);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkRolesAllowedUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"rolesAllowedUserOrAdmin");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRolesAllowedUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"rolesAllowedUserOrAdmin");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
methodAuthorizationContext);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRolesAllowedUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
"ROLE_ANONYMOUS");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"rolesAllowedUserOrAdmin");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodAuthorizationContext);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
public static class TestClass {
public void doSomething() {
}
@DenyAll
@RolesAllowed("ADMIN")
public void denyAllRolesAllowedAdmin() {
}
@PermitAll
@RolesAllowed("ADMIN")
public void permitAllRolesAllowedAdmin() {
}
@RolesAllowed({ "USER", "ADMIN" })
public void rolesAllowedUserOrAdmin() {
}
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2002-2021 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
*
* https://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.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.security.RolesAllowed;
import org.springframework.security.access.prepost.PreAuthorize;
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ADMIN')")
@RolesAllowed("ADMIN")
@Secured("ADMIN")
public @interface RequireAdminRole {
}
@@ -0,0 +1,32 @@
/*
* Copyright 2002-2021 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
*
* https://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.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.security.RolesAllowed;
import org.springframework.security.access.prepost.PreAuthorize;
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('USER')")
@RolesAllowed("ADMIN")
@Secured("USER")
public @interface RequireUserRole {
}
@@ -1,104 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.annotation;
import java.util.function.Supplier;
import org.junit.Test;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SecuredAuthorizationManager}.
*
* @author Evgeniy Cheban
*/
public class SecuredAuthorizationManagerTests {
@Test
public void checkDoSomethingWhenNoSecuredAnnotationThenNullDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
assertThat(decision).isNull();
}
@Test
public void checkSecuredUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"securedUserOrAdmin");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkSecuredUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"securedUserOrAdmin");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
methodAuthorizationContext);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkSecuredUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
"ROLE_ANONYMOUS");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"securedUserOrAdmin");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
TestClass.class);
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodAuthorizationContext);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
public static class TestClass {
public void doSomething() {
}
@Secured({ "ROLE_USER", "ROLE_ADMIN" })
public void securedUserOrAdmin() {
}
}
}
@@ -1,107 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.aopalliance;
import java.util.function.Supplier;
import org.junit.After;
import org.junit.Test;
import org.springframework.aop.MethodMatcher;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* Tests for {@link AuthorizationMethodInterceptor}.
*
* @author Evgeniy Cheban
*/
public class AuthorizationMethodInterceptorTests {
@After
public void tearDown() {
SecurityContextHolder.clearContext();
}
@Test
public void invokeWhenAuthenticatedThenVerifyAdvicesUsage() throws Throwable {
Authentication authentication = TestAuthentication.authenticatedUser();
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString");
AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> mockBeforeAdvice = mock(
AuthorizationMethodBeforeAdvice.class);
AuthorizationMethodAfterAdvice<MethodAuthorizationContext> mockAfterAdvice = mock(
AuthorizationMethodAfterAdvice.class);
given(mockAfterAdvice.after(any(), any(MethodAuthorizationContext.class), eq(null))).willReturn("abc");
AuthorizationMethodInterceptor interceptor = new AuthorizationMethodInterceptor(mockBeforeAdvice,
mockAfterAdvice);
Object result = interceptor.invoke(mockMethodInvocation);
assertThat(result).isEqualTo("abc");
verify(mockAfterAdvice).after(any(), any(MethodAuthorizationContext.class), eq(null));
}
@Test
public void invokeWhenNotAuthenticatedThenAuthenticationCredentialsNotFoundException() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString");
AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> beforeAdvice = new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
@Override
public MethodMatcher getMethodMatcher() {
return MethodMatcher.TRUE;
}
@Override
public void before(Supplier<Authentication> authentication,
MethodAuthorizationContext methodAuthorizationContext) {
authentication.get();
}
};
AuthorizationMethodAfterAdvice<MethodAuthorizationContext> mockAfterAdvice = mock(
AuthorizationMethodAfterAdvice.class);
AuthorizationMethodInterceptor interceptor = new AuthorizationMethodInterceptor(beforeAdvice, mockAfterAdvice);
assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class)
.isThrownBy(() -> interceptor.invoke(mockMethodInvocation))
.withMessage("An Authentication object was not found in the SecurityContext");
verifyNoInteractions(mockAfterAdvice);
}
public static class TestClass {
public String doSomethingString() {
return null;
}
}
}
@@ -1,164 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.method;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.junit.Test;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.support.StaticMethodMatcher;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DelegatingAuthorizationMethodAfterAdvice}.
*
* @author Evgeniy Cheban
*/
public class DelegatingAuthorizationMethodAfterAdviceTests {
@Test
public void methodMatcherWhenNoneMatchesThenNotMatches() throws Exception {
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
@Override
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
Object returnedObject) {
return returnedObject;
}
@Override
public MethodMatcher getMethodMatcher() {
return new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return false;
}
};
}
});
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
@Override
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
Object returnedObject) {
return returnedObject;
}
@Override
public MethodMatcher getMethodMatcher() {
return new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return false;
}
};
}
});
DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates);
MethodMatcher methodMatcher = advice.getMethodMatcher();
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
}
@Test
public void methodMatcherWhenAnyMatchesThenMatches() throws Exception {
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
@Override
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
Object returnedObject) {
return returnedObject;
}
@Override
public MethodMatcher getMethodMatcher() {
return new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return false;
}
};
}
});
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
@Override
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
Object returnedObject) {
return returnedObject;
}
@Override
public MethodMatcher getMethodMatcher() {
return MethodMatcher.TRUE;
}
});
DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates);
MethodMatcher methodMatcher = advice.getMethodMatcher();
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isTrue();
}
@Test
public void checkWhenDelegatingAdviceModifiesReturnedObjectThenModifiedReturnedObject() throws Exception {
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
@Override
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
Object returnedObject) {
return returnedObject + "b";
}
@Override
public MethodMatcher getMethodMatcher() {
return MethodMatcher.TRUE;
}
});
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
@Override
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
Object returnedObject) {
return returnedObject + "c";
}
@Override
public MethodMatcher getMethodMatcher() {
return MethodMatcher.TRUE;
}
});
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates);
Object result = advice.after(TestAuthentication::authenticatedUser, methodAuthorizationContext, "a");
assertThat(result).isEqualTo("abc");
}
public static class TestClass {
public String doSomething() {
return null;
}
}
}
@@ -1,168 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.method;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import org.junit.Test;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.support.StaticMethodMatcher;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link DelegatingAuthorizationMethodBeforeAdvice}.
*
* @author Evgeniy Cheban
*/
public class DelegatingAuthorizationMethodBeforeAdviceTests {
@Test
public void methodMatcherWhenNoneMatchesThenNotMatches() throws Exception {
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
@Override
public MethodMatcher getMethodMatcher() {
return new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return false;
}
};
}
@Override
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
}
});
delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
@Override
public MethodMatcher getMethodMatcher() {
return new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return false;
}
};
}
@Override
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
}
});
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
MethodMatcher methodMatcher = advice.getMethodMatcher();
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
}
@Test
public void methodMatcherWhenAnyMatchesThenMatches() throws Exception {
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
@Override
public MethodMatcher getMethodMatcher() {
return new StaticMethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return false;
}
};
}
@Override
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
}
});
delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
@Override
public MethodMatcher getMethodMatcher() {
return MethodMatcher.TRUE;
}
@Override
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
}
});
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
MethodMatcher methodMatcher = advice.getMethodMatcher();
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isTrue();
}
@Test
public void checkWhenAllGrantsOrAbstainsThenPasses() throws Exception {
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null));
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE,
(a, o) -> new AuthorizationDecision(true)));
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null));
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
}
@Test
public void checkWhenAnyDeniesThenAccessDeniedException() throws Exception {
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null));
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE,
(a, o) -> new AuthorizationDecision(true)));
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE,
(a, o) -> new AuthorizationDecision(false)));
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
.withMessage("Access Denied");
}
@Test
public void checkWhenDelegatesEmptyThenPasses() throws Exception {
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(
Collections.emptyList());
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething");
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
}
public static class TestClass {
public void doSomething() {
}
}
}
@@ -14,55 +14,56 @@
* limitations under the License.
*/
package org.springframework.security.access.method;
import java.util.function.Supplier;
package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.springframework.aop.MethodMatcher;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.aop.Pointcut;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AuthorizationManagerMethodAfterAdvice}.
* Tests for {@link AuthorizationManagerAfterMethodInterceptor}.
*
* @author Evgeniy Cheban
*/
public class AuthorizationManagerMethodAfterAdviceTests {
public class AuthorizationManagerAfterMethodInterceptorTests {
@Test
public void instantiateWhenMethodMatcherNullThenException() {
AuthorizationManager<MethodInvocationResult> mockAuthorizationManager = mock(AuthorizationManager.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(null, mock(AuthorizationManager.class)))
.withMessage("methodMatcher cannot be null");
.isThrownBy(() -> new AuthorizationManagerAfterMethodInterceptor(null, mockAuthorizationManager))
.withMessage("pointcut cannot be null");
}
@Test
public void instantiateWhenAuthorizationManagerNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(mock(MethodMatcher.class), null))
.isThrownBy(() -> new AuthorizationManagerAfterMethodInterceptor(mock(Pointcut.class), null))
.withMessage("authorizationManager cannot be null");
}
@Test
public void beforeWhenMockAuthorizationManagerThenVerifyAndReturnedObject() {
Supplier<Authentication> authentication = TestAuthentication::authenticatedUser;
public void beforeWhenMockAuthorizationManagerThenVerifyAndReturnedObject() throws Throwable {
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
Object returnedObject = new Object();
AuthorizationManager<MethodInvocation> mockAuthorizationManager = mock(AuthorizationManager.class);
AuthorizationManagerMethodAfterAdvice<MethodInvocation> advice = new AuthorizationManagerMethodAfterAdvice<>(
mock(MethodMatcher.class), mockAuthorizationManager);
Object result = advice.after(authentication, mockMethodInvocation, returnedObject);
assertThat(result).isEqualTo(returnedObject);
verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation);
MethodInvocationResult result = new MethodInvocationResult(mockMethodInvocation, new Object());
given(mockMethodInvocation.proceed()).willReturn(result.getResult());
AuthorizationManager<MethodInvocationResult> mockAuthorizationManager = mock(AuthorizationManager.class);
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(
Pointcut.TRUE, mockAuthorizationManager);
Object returnedObject = advice.invoke(mockMethodInvocation);
assertThat(returnedObject).isEqualTo(result.getResult());
verify(mockAuthorizationManager).verify(eq(AuthorizationManagerAfterMethodInterceptor.AUTHENTICATION_SUPPLIER),
any(MethodInvocationResult.class));
}
}
@@ -14,52 +14,49 @@
* limitations under the License.
*/
package org.springframework.security.access.method;
import java.util.function.Supplier;
package org.springframework.security.authorization.method;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.springframework.aop.MethodMatcher;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.aop.Pointcut;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AuthorizationManagerMethodBeforeAdvice}.
* Tests for {@link AuthorizationManagerBeforeMethodInterceptor}.
*
* @author Evgeniy Cheban
*/
public class AuthorizationManagerMethodBeforeAdviceTests {
public class AuthorizationManagerBeforeMethodInterceptorTests {
@Test
public void instantiateWhenMethodMatcherNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(null, mock(AuthorizationManager.class)))
.withMessage("methodMatcher cannot be null");
.isThrownBy(
() -> new AuthorizationManagerBeforeMethodInterceptor(null, mock(AuthorizationManager.class)))
.withMessage("pointcut cannot be null");
}
@Test
public void instantiateWhenAuthorizationManagerNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(mock(MethodMatcher.class), null))
.isThrownBy(() -> new AuthorizationManagerBeforeMethodInterceptor(mock(Pointcut.class), null))
.withMessage("authorizationManager cannot be null");
}
@Test
public void beforeWhenMockAuthorizationManagerThenVerify() {
Supplier<Authentication> authentication = TestAuthentication::authenticatedUser;
public void beforeWhenMockAuthorizationManagerThenVerify() throws Throwable {
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
AuthorizationManager<MethodInvocation> mockAuthorizationManager = mock(AuthorizationManager.class);
AuthorizationManagerMethodBeforeAdvice<MethodInvocation> advice = new AuthorizationManagerMethodBeforeAdvice<>(
mock(MethodMatcher.class), mockAuthorizationManager);
advice.before(authentication, mockMethodInvocation);
verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation);
AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
Pointcut.TRUE, mockAuthorizationManager);
advice.invoke(mockMethodInvocation);
verify(mockAuthorizationManager).verify(AuthorizationManagerBeforeMethodInterceptor.AUTHENTICATION_SUPPLIER,
mockMethodInvocation);
}
}
@@ -0,0 +1,167 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.Test;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AopUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AuthorizationMethodPointcuts}
*/
public class AuthorizationMethodPointcutsTests {
@Test
public void forAnnotationsWhenAnnotationThenClassBasedAnnotationPointcut() {
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
assertThat(AopUtils.canApply(preAuthorize, ClassController.class)).isTrue();
assertThat(AopUtils.canApply(preAuthorize, NoController.class)).isFalse();
}
@Test
public void forAnnotationsWhenAnnotationThenMethodBasedAnnotationPointcut() {
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
assertThat(AopUtils.canApply(preAuthorize, MethodController.class)).isTrue();
}
@Test
public void forAnnotationsWhenAnnotationThenClassInheritancePointcut() {
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
assertThat(AopUtils.canApply(preAuthorize, InterfacedClassController.class)).isTrue();
}
@Test
public void forAnnotationsWhenAnnotationThenMethodInheritancePointcut() {
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
assertThat(AopUtils.canApply(preAuthorize, InterfacedMethodController.class)).isTrue();
}
@Test
public void forAnnotationsWhenAnnotationThenAnnotationClassInheritancePointcut() {
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
assertThat(AopUtils.canApply(preAuthorize, InterfacedAnnotationClassController.class)).isTrue();
}
@Test
public void forAnnotationsWhenAnnotationThenAnnotationMethodInheritancePointcut() {
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
assertThat(AopUtils.canApply(preAuthorize, InterfacedAnnotationMethodController.class)).isTrue();
}
@PreAuthorize("hasAuthority('APP')")
public static class ClassController {
String methodOne(String paramOne) {
return "value";
}
}
public static class MethodController {
@PreAuthorize("hasAuthority('APP')")
String methodOne(String paramOne) {
return "value";
}
}
public static class NoController {
String methodOne(String paramOne) {
return "value";
}
}
@PreAuthorize("hasAuthority('APP')")
public interface ClassControllerInterface {
String methodOne(String paramOne);
}
public static class InterfacedClassController implements ClassControllerInterface {
public String methodOne(String paramOne) {
return "value";
}
}
public interface MethodControllerInterface {
@PreAuthorize("hasAuthority('APP')")
String methodOne(String paramOne);
}
public static class InterfacedMethodController implements MethodControllerInterface {
public String methodOne(String paramOne) {
return "value";
}
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAuthority('APP')")
@interface MyAnnotation {
}
@MyAnnotation
public interface ClassAnnotationControllerInterface {
String methodOne(String paramOne);
}
public static class InterfacedAnnotationClassController implements ClassAnnotationControllerInterface {
public String methodOne(String paramOne) {
return "value";
}
}
public interface MethodAnnotationControllerInterface {
@MyAnnotation
String methodOne(String paramOne);
}
public static class InterfacedAnnotationMethodController implements MethodAnnotationControllerInterface {
public String methodOne(String paramOne) {
return "value";
}
}
}
@@ -0,0 +1,278 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Supplier;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link Jsr250AuthorizationManager}.
*
* @author Evgeniy Cheban
*/
public class Jsr250AuthorizationManagerTests {
@Test
public void rolePrefixWhenNotSetThenDefaultsToRole() {
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
assertThat(manager).extracting("rolePrefix").isEqualTo("ROLE_");
}
@Test
public void setRolePrefixWhenNullThenException() {
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
assertThatIllegalArgumentException().isThrownBy(() -> manager.setRolePrefix(null))
.withMessage("rolePrefix cannot be null");
}
@Test
public void setRolePrefixWhenNotNullThenSets() {
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
manager.setRolePrefix("CUSTOM_");
assertThat(manager).extracting("rolePrefix").isEqualTo("CUSTOM_");
}
@Test
public void checkDoSomethingWhenNoJsr250AnnotationsThenNullDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision).isNull();
}
@Test
public void checkPermitAllWhenRoleUserThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "permitAll");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkDenyAllWhenRoleAdminThenDeniedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "denyAll");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkRolesAllowedUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"rolesAllowedUserOrAdmin");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRolesAllowedUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"rolesAllowedUserOrAdmin");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRolesAllowedUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
"ROLE_ANONYMOUS");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"rolesAllowedUserOrAdmin");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkMultipleAnnotationsWhenInvokedThenAnnotationConfigurationException() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
"ROLE_ANONYMOUS");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"multipleAnnotations");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
@Test
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "rolesAllowedAdmin");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isFalse();
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkDeniedWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "denyAll");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "rolesAllowedUser");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isTrue();
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "inheritedAnnotations");
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {
}
@DenyAll
public void denyAll() {
}
@PermitAll
public void permitAll() {
}
@RolesAllowed({ "USER", "ADMIN" })
public void rolesAllowedUserOrAdmin() {
}
@RolesAllowed("USER")
@DenyAll
public void multipleAnnotations() {
}
public void inheritedAnnotations() {
}
}
@RolesAllowed("USER")
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
@RolesAllowed("ADMIN")
public void rolesAllowedAdmin() {
}
@DenyAll
public void denyAll() {
}
public void rolesAllowedUser() {
}
@Override
@PermitAll
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@RolesAllowed("ADMIN")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@MyRolesAllowed
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@DenyAll
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@RolesAllowed("USER")
public @interface MyRolesAllowed {
}
}
@@ -16,21 +16,27 @@
package org.springframework.security.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
@@ -57,38 +63,32 @@ public class PostAuthorizeAuthorizationManagerTests {
@Test
public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething", new Class[] {}, new Object[] {});
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
assertThat(decision).isNull();
}
@Test
public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString", new Class[] { String.class }, new Object[] { "grant" });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@@ -96,14 +96,11 @@ public class PostAuthorizeAuthorizationManagerTests {
@Test
public void checkDoSomethingListWhenReturnObjectContainsGrantThenGrantedDecision() throws Exception {
List<String> list = Arrays.asList("grant", "deny");
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingList", new Class[] { List.class }, new Object[] { list });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
methodAuthorizationContext.setReturnObject(list);
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, list);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@@ -111,19 +108,66 @@ public class PostAuthorizeAuthorizationManagerTests {
@Test
public void checkDoSomethingListWhenReturnObjectNotContainsGrantThenDeniedDecision() throws Exception {
List<String> list = Collections.singletonList("deny");
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingList", new Class[] { List.class }, new Object[] { list });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
methodAuthorizationContext.setReturnObject(list);
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, list);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
public static class TestClass {
@Test
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedAdmin");
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, result);
assertThat(decision.isGranted()).isFalse();
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
decision = manager.check(authentication, result);
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedUser");
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, result);
assertThat(decision.isGranted()).isTrue();
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
decision = manager.check(authentication, result);
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, result));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "inheritedAnnotations");
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, result));
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {
@@ -139,6 +183,58 @@ public class PostAuthorizeAuthorizationManagerTests {
return list;
}
@Override
public void inheritedAnnotations() {
}
}
@PostAuthorize("hasRole('USER')")
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
@PostAuthorize("hasRole('ADMIN')")
public void securedAdmin() {
}
public void securedUser() {
}
@Override
@PostAuthorize("hasRole('ADMIN')")
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@PostAuthorize("hasRole('ADMIN')")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@PostAuthorize("hasRole('USER')")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@MyPostAuthorize
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@PostAuthorize("hasRole('USER')")
public @interface MyPostAuthorize {
}
}
@@ -1,96 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.Test;
import org.springframework.aop.MethodMatcher;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.authentication.TestAuthentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link PostFilterAuthorizationMethodAfterAdvice}.
*
* @author Evgeniy Cheban
*/
public class PostFilterAuthorizationMethodAfterAdviceTests {
@Test
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
advice.setExpressionHandler(expressionHandler);
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
public void setExpressionHandlerWhenNullThenException() {
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
.withMessage("expressionHandler cannot be null");
}
@Test
public void methodMatcherWhenMethodHasNotPostFilterAnnotationThenNotMatches() throws Exception {
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
MethodMatcher methodMatcher = advice.getMethodMatcher();
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
}
@Test
public void methodMatcherWhenMethodHasPostFilterAnnotationThenMatches() throws Exception {
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
MethodMatcher methodMatcher = advice.getMethodMatcher();
assertThat(
methodMatcher.matches(TestClass.class.getMethod("doSomethingArray", String[].class), TestClass.class))
.isTrue();
}
@Test
public void afterWhenArrayNotNullThenFilteredArray() throws Exception {
String[] array = { "john", "bob" };
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingArray", new Class[] { String[].class }, new Object[] { array });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
Object result = advice.after(TestAuthentication::authenticatedUser, methodAuthorizationContext, array);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.array(String[].class)).containsOnly("john");
}
public static class TestClass {
public void doSomething() {
}
@PostFilter("filterObject == 'john'")
public String[] doSomethingArray(String[] array) {
return array;
}
}
}
@@ -0,0 +1,186 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.MethodMatcher;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.core.context.SecurityContextHolder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link PostFilterAuthorizationMethodInterceptor}.
*
* @author Evgeniy Cheban
*/
public class PostFilterAuthorizationMethodInterceptorTests {
@Before
public void setUp() {
SecurityContextHolder.getContext().setAuthentication(TestAuthentication.authenticatedUser());
}
@After
public void tearDown() {
SecurityContextHolder.clearContext();
}
@Test
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
advice.setExpressionHandler(expressionHandler);
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
public void setExpressionHandlerWhenNullThenException() {
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
.withMessage("expressionHandler cannot be null");
}
@Test
public void methodMatcherWhenMethodHasNotPostFilterAnnotationThenNotMatches() throws Exception {
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher();
assertThat(methodMatcher.matches(NoPostFilterClass.class.getMethod("doSomething"), NoPostFilterClass.class))
.isFalse();
}
@Test
public void methodMatcherWhenMethodHasPostFilterAnnotationThenMatches() throws Exception {
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher();
assertThat(
methodMatcher.matches(TestClass.class.getMethod("doSomethingArray", String[].class), TestClass.class))
.isTrue();
}
@Test
public void afterWhenArrayNotNullThenFilteredArray() throws Throwable {
String[] array = { "john", "bob" };
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingArrayClassLevel", new Class[] { String[].class }, new Object[] { array }) {
@Override
public Object proceed() {
return array;
}
};
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
Object result = advice.invoke(methodInvocation);
assertThat(result).asInstanceOf(InstanceOfAssertFactories.array(String[].class)).containsOnly("john");
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> advice.invoke(methodInvocation));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ConflictingAnnotations(),
ConflictingAnnotations.class, "inheritedAnnotations");
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> advice.invoke(methodInvocation));
}
@PostFilter("filterObject == 'john'")
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
@PostFilter("filterObject == 'john'")
public String[] doSomethingArray(String[] array) {
return array;
}
public String[] doSomethingArrayClassLevel(String[] array) {
return array;
}
@Override
public void inheritedAnnotations() {
}
}
public static class NoPostFilterClass {
public void doSomething() {
}
}
public static class ConflictingAnnotations implements InterfaceAnnotationsThree {
@Override
@PostFilter("filterObject == 'jack'")
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@PostFilter("filterObject == 'jim'")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@PostFilter("filterObject == 'jane'")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@MyPostFilter
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@PostFilter("filterObject == 'john'")
public @interface MyPostFilter {
}
}
@@ -16,17 +16,24 @@
package org.springframework.security.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Supplier;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
@@ -53,43 +60,80 @@ public class PreAuthorizeAuthorizationManagerTests {
@Test
public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething", new Class[] {}, new Object[] {});
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision).isNull();
}
@Test
public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString", new Class[] { String.class }, new Object[] { "grant" });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
methodAuthorizationContext);
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
public static class TestClass {
@Test
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedAdmin");
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isFalse();
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedUser");
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isTrue();
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "inheritedAnnotations");
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {
@@ -100,6 +144,58 @@ public class PreAuthorizeAuthorizationManagerTests {
return s;
}
@Override
public void inheritedAnnotations() {
}
}
@PreAuthorize("hasRole('USER')")
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
@PreAuthorize("hasRole('ADMIN')")
public void securedAdmin() {
}
public void securedUser() {
}
@Override
@PreAuthorize("hasRole('ADMIN')")
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@PreAuthorize("hasRole('ADMIN')")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@PreAuthorize("hasRole('USER')")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@MyPreAuthorize
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('USER')")
public @interface MyPreAuthorize {
}
}
@@ -1,200 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.aop.MethodMatcher;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.method.MethodAuthorizationContext;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authentication.TestAuthentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link PreFilterAuthorizationMethodBeforeAdvice}.
*
* @author Evgeniy Cheban
*/
public class PreFilterAuthorizationMethodBeforeAdviceTests {
@Test
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
advice.setExpressionHandler(expressionHandler);
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
public void setExpressionHandlerWhenNullThenException() {
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
.withMessage("expressionHandler cannot be null");
}
@Test
public void methodMatcherWhenMethodHasNotPreFilterAnnotationThenNotMatches() throws Exception {
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
MethodMatcher methodMatcher = advice.getMethodMatcher();
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
}
@Test
public void methodMatcherWhenMethodHasPreFilterAnnotationThenMatches() throws Exception {
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
MethodMatcher methodMatcher = advice.getMethodMatcher();
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomethingListFilterTargetMatch", List.class),
TestClass.class)).isTrue();
}
@Test
public void findFilterTargetWhenNameProvidedAndNotMatchThenException() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetNotMatch", new Class[] { List.class }, new Object[] { new ArrayList<>() });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
assertThatIllegalArgumentException()
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
.withMessage(
"Filter target was null, or no argument with name 'filterTargetNotMatch' found in method.");
}
@Test
public void findFilterTargetWhenNameProvidedAndMatchAndNullThenException() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { null });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
assertThatIllegalArgumentException()
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
.withMessage("Filter target was null, or no argument with name 'list' found in method.");
}
@Test
public void findFilterTargetWhenNameProvidedAndMatchAndNotNullThenFiltersList() throws Exception {
List<String> list = new ArrayList<>();
list.add("john");
list.add("bob");
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { list });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
assertThat(list).hasSize(1);
assertThat(list.get(0)).isEqualTo("john");
}
@Test
public void findFilterTargetWhenNameNotProvidedAndSingleArgListNullThenException() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { null });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
assertThatIllegalArgumentException()
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
.withMessage("Filter target was null. Make sure you passing the correct value in the method argument.");
}
@Test
public void findFilterTargetWhenNameNotProvidedAndSingleArgListThenFiltersList() throws Exception {
List<String> list = new ArrayList<>();
list.add("john");
list.add("bob");
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { list });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
assertThat(list).hasSize(1);
assertThat(list.get(0)).isEqualTo("john");
}
@Test
public void findFilterTargetWhenNameNotProvidedAndSingleArgArrayThenException() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingArrayFilterTargetNotProvided", new Class[] { String[].class },
new Object[] { new String[] {} });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
assertThatIllegalStateException()
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
.withMessage(
"Pre-filtering on array types is not supported. Using a Collection will solve this problem.");
}
@Test
public void findFilterTargetWhenNameNotProvidedAndNotSingleArgThenException() throws Exception {
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingTwoArgsFilterTargetNotProvided", new Class[] { String.class, List.class },
new Object[] { "", new ArrayList<>() });
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
TestClass.class);
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
assertThatIllegalStateException()
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
.withMessage("Unable to determine the method argument for filtering. Specify the filter target.");
}
public static class TestClass {
public void doSomething() {
}
@PreFilter(value = "filterObject == 'john'", filterTarget = "filterTargetNotMatch")
public List<String> doSomethingListFilterTargetNotMatch(List<String> list) {
return list;
}
@PreFilter(value = "filterObject == 'john'", filterTarget = "list")
public List<String> doSomethingListFilterTargetMatch(List<String> list) {
return list;
}
@PreFilter("filterObject == 'john'")
public List<String> doSomethingListFilterTargetNotProvided(List<String> list) {
return list;
}
@PreFilter("filterObject == 'john'")
public String[] doSomethingArrayFilterTargetNotProvided(String[] array) {
return array;
}
@PreFilter("filterObject == 'john'")
public List<String> doSomethingTwoArgsFilterTargetNotProvided(String s, List<String> list) {
return list;
}
}
}
@@ -0,0 +1,260 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.MethodMatcher;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.core.context.SecurityContextHolder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link PreFilterAuthorizationMethodInterceptor}.
*
* @author Evgeniy Cheban
*/
public class PreFilterAuthorizationMethodInterceptorTests {
@Before
public void setUp() {
SecurityContextHolder.getContext().setAuthentication(TestAuthentication.authenticatedUser());
}
@After
public void tearDown() {
SecurityContextHolder.clearContext();
}
@Test
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
advice.setExpressionHandler(expressionHandler);
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
}
@Test
public void setExpressionHandlerWhenNullThenException() {
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
.withMessage("expressionHandler cannot be null");
}
@Test
public void methodMatcherWhenMethodHasNotPreFilterAnnotationThenNotMatches() throws Exception {
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher();
assertThat(methodMatcher.matches(NoPreFilterClass.class.getMethod("doSomething"), NoPreFilterClass.class))
.isFalse();
}
@Test
public void methodMatcherWhenMethodHasPreFilterAnnotationThenMatches() throws Exception {
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher();
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomethingListFilterTargetMatch", List.class),
TestClass.class)).isTrue();
}
@Test
public void findFilterTargetWhenNameProvidedAndNotMatchThenException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetNotMatch", new Class[] { List.class }, new Object[] { new ArrayList<>() });
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> advice.invoke(methodInvocation)).withMessage(
"Filter target was null, or no argument with name 'filterTargetNotMatch' found in method.");
}
@Test
public void findFilterTargetWhenNameProvidedAndMatchAndNullThenException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { null });
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> advice.invoke(methodInvocation))
.withMessage("Filter target was null, or no argument with name 'list' found in method.");
}
@Test
public void findFilterTargetWhenNameProvidedAndMatchAndNotNullThenFiltersList() throws Throwable {
List<String> list = new ArrayList<>();
list.add("john");
list.add("bob");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { list });
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
advice.invoke(methodInvocation);
assertThat(list).hasSize(1);
assertThat(list.get(0)).isEqualTo("john");
}
@Test
public void findFilterTargetWhenNameNotProvidedAndSingleArgListNullThenException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { null });
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
assertThatIllegalArgumentException().isThrownBy(() -> advice.invoke(methodInvocation))
.withMessage("Filter target was null. Make sure you passing the correct value in the method argument.");
}
@Test
public void findFilterTargetWhenNameNotProvidedAndSingleArgListThenFiltersList() throws Throwable {
List<String> list = new ArrayList<>();
list.add("john");
list.add("bob");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { list });
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
advice.invoke(methodInvocation);
assertThat(list).hasSize(1);
assertThat(list.get(0)).isEqualTo("john");
}
@Test
public void findFilterTargetWhenNameNotProvidedAndSingleArgArrayThenException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingArrayFilterTargetNotProvided", new Class[] { String[].class },
new Object[] { new String[] {} });
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
assertThatIllegalStateException().isThrownBy(() -> advice.invoke(methodInvocation)).withMessage(
"Pre-filtering on array types is not supported. Using a Collection will solve this problem.");
}
@Test
public void findFilterTargetWhenNameNotProvidedAndNotSingleArgThenException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomethingTwoArgsFilterTargetNotProvided", new Class[] { String.class, List.class },
new Object[] { "", new ArrayList<>() });
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
assertThatIllegalStateException().isThrownBy(() -> advice.invoke(methodInvocation))
.withMessage("Unable to determine the method argument for filtering. Specify the filter target.");
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> advice.invoke(methodInvocation));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ConflictingAnnotations(),
ConflictingAnnotations.class, "inheritedAnnotations");
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> advice.invoke(methodInvocation));
}
@PreFilter("filterObject == 'john'")
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
@PreFilter(value = "filterObject == 'john'", filterTarget = "filterTargetNotMatch")
public List<String> doSomethingListFilterTargetNotMatch(List<String> list) {
return list;
}
@PreFilter(value = "filterObject == 'john'", filterTarget = "list")
public List<String> doSomethingListFilterTargetMatch(List<String> list) {
return list;
}
@PreFilter("filterObject == 'john'")
public List<String> doSomethingListFilterTargetNotProvided(List<String> list) {
return list;
}
@PreFilter("filterObject == 'john'")
public String[] doSomethingArrayFilterTargetNotProvided(String[] array) {
return array;
}
public List<String> doSomethingTwoArgsFilterTargetNotProvided(String s, List<String> list) {
return list;
}
@Override
public void inheritedAnnotations() {
}
}
public static class NoPreFilterClass {
public void doSomething() {
}
}
public static class ConflictingAnnotations implements InterfaceAnnotationsThree {
@Override
@PreFilter("filterObject == 'jack'")
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@PreFilter("filterObject == 'jim'")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@PreFilter("filterObject == 'jane'")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@MyPreFilter
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@PreFilter("filterObject == 'john'")
public @interface MyPreFilter {
}
}
@@ -0,0 +1,195 @@
/*
* Copyright 2002-2021 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
*
* https://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.authorization.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.function.Supplier;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.intercept.method.MockMethodInvocation;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link SecuredAuthorizationManager}.
*
* @author Evgeniy Cheban
*/
public class SecuredAuthorizationManagerTests {
@Test
public void checkDoSomethingWhenNoSecuredAnnotationThenNullDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"doSomething");
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision).isNull();
}
@Test
public void checkSecuredUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"securedUserOrAdmin");
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkSecuredUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"securedUserOrAdmin");
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkSecuredUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
"ROLE_ANONYMOUS");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"securedUserOrAdmin");
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
assertThat(decision).isNotNull();
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedAdmin");
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isFalse();
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isTrue();
}
@Test
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "securedUser");
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isTrue();
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
decision = manager.check(authentication, methodInvocation);
assertThat(decision.isGranted()).isFalse();
}
@Test
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
"inheritedAnnotations");
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
@Test
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
ClassLevelAnnotations.class, "inheritedAnnotations");
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> manager.check(authentication, methodInvocation));
}
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
public void doSomething() {
}
@Secured({ "ROLE_USER", "ROLE_ADMIN" })
public void securedUserOrAdmin() {
}
@Override
public void inheritedAnnotations() {
}
}
@Secured("ROLE_USER")
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
@Secured("ROLE_ADMIN")
public void securedAdmin() {
}
public void securedUser() {
}
@Override
@Secured("ROLE_ADMIN")
public void inheritedAnnotations() {
}
}
public interface InterfaceAnnotationsOne {
@Secured("ROLE_ADMIN")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsTwo {
@Secured("ROLE_USER")
void inheritedAnnotations();
}
public interface InterfaceAnnotationsThree {
@MySecured
void inheritedAnnotations();
}
@Retention(RetentionPolicy.RUNTIME)
@Secured("ROLE_USER")
public @interface MySecured {
}
}