Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring Security's method security meta-annotation support allowed for parameters. #14492

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
Expand Down Expand Up @@ -52,6 +52,7 @@
*
* @author Luke Taylor
* @author Evgeniy Cheban
* @author DingHao
* @since 3.0
*/
public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpressionHandler<MethodInvocation>
Expand All @@ -65,6 +66,8 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr

private PermissionCacheOptimizer permissionCacheOptimizer = null;

private Map<String, Object> variables = null;

private String defaultRolePrefix = "ROLE_";

public DefaultMethodSecurityExpressionHandler() {
Expand All @@ -76,14 +79,14 @@ public DefaultMethodSecurityExpressionHandler() {
*/
@Override
public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) {
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer(), this.variables);
}

@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
MethodSecurityExpressionOperations root = createSecurityExpressionRoot(authentication, mi);
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(root, mi,
getParameterNameDiscoverer());
getParameterNameDiscoverer(), this.variables);
ctx.setBeanResolver(getBeanResolver());
return ctx;
}
Expand Down Expand Up @@ -245,6 +248,11 @@ public void setReturnObject(Object returnObject, EvaluationContext ctx) {
((MethodSecurityExpressionOperations) ctx.getRootObject().getValue()).setReturnObject(returnObject);
}

@Override
public void setVariables(Map<String, Object> variables) {
this.variables = variables;
}

/**
* <p>
* Sets the default prefix to be added to
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -17,6 +17,7 @@
package org.springframework.security.access.expression.method;

import java.lang.reflect.Method;
import java.util.Map;

import org.aopalliance.intercept.MethodInvocation;

Expand All @@ -35,10 +36,13 @@
* @author Luke Taylor
* @author Daniel Bustamante
* @author Evgeniy Cheban
* @author DingHao
* @since 3.0
*/
class MethodSecurityEvaluationContext extends MethodBasedEvaluationContext {

private final Map<String, Object> variables;

/**
* Intended for testing. Don't use in practice as it creates a new parameter resolver
* for each instance. Use the constructor which takes the resolver, as an argument
Expand All @@ -50,16 +54,36 @@ class MethodSecurityEvaluationContext extends MethodBasedEvaluationContext {

MethodSecurityEvaluationContext(Authentication user, MethodInvocation mi,
ParameterNameDiscoverer parameterNameDiscoverer) {
this(user, mi, parameterNameDiscoverer, null);
}

MethodSecurityEvaluationContext(Authentication user, MethodInvocation mi,
ParameterNameDiscoverer parameterNameDiscoverer, Map<String, Object> variables) {
super(mi.getThis(), getSpecificMethod(mi), mi.getArguments(), parameterNameDiscoverer);
this.variables = variables;
}

MethodSecurityEvaluationContext(MethodSecurityExpressionOperations root, MethodInvocation mi,
ParameterNameDiscoverer parameterNameDiscoverer) {
this(root, mi, parameterNameDiscoverer, null);
}

MethodSecurityEvaluationContext(MethodSecurityExpressionOperations root, MethodInvocation mi,
ParameterNameDiscoverer parameterNameDiscoverer, Map<String, Object> variables) {
super(root, getSpecificMethod(mi), mi.getArguments(), parameterNameDiscoverer);
this.variables = variables;
}

private static Method getSpecificMethod(MethodInvocation mi) {
return AopUtils.getMostSpecificMethod(mi.getMethod(), AopProxyUtils.ultimateTargetClass(mi.getThis()));
}

@Override
protected void lazyLoadArguments() {
if (this.variables != null) {
setVariables(this.variables);
}
super.lazyLoadArguments();
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -16,6 +16,8 @@

package org.springframework.security.access.expression.method;

import java.util.Map;

import org.aopalliance.intercept.MethodInvocation;

import org.springframework.expression.EvaluationContext;
Expand All @@ -27,6 +29,7 @@
* method invocations.
*
* @author Luke Taylor
* @author DingHao
* @since 3.0
*/
public interface MethodSecurityExpressionHandler extends SecurityExpressionHandler<MethodInvocation> {
Expand All @@ -53,4 +56,12 @@ public interface MethodSecurityExpressionHandler extends SecurityExpressionHandl
*/
void setReturnObject(Object returnObject, EvaluationContext ctx);

/**
* Set multiple named variables in this evaluation context to given values.
* <p>
* Note: the variables has a lower priority than the method parameter priority
* @param variables the names and values of the variables to set
*/
void setVariables(Map<String, Object> variables);

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -23,12 +23,14 @@
import org.aopalliance.intercept.MethodInvocation;

import org.springframework.core.MethodClassKey;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.lang.NonNull;

/**
* For internal use only, as this contract is likely to change
*
* @author Evgeniy Cheban
* @author DingHao
*/
abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute> {

Expand Down Expand Up @@ -67,4 +69,12 @@ final T getAttribute(Method method, Class<?> targetClass) {
@NonNull
abstract T resolveAttribute(Method method, Class<?> targetClass);

Map<String, Object> getMetaAnnotationAttribute(MergedAnnotation<?> mergedAnnotation) {
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
if (metaSource != null) {
return metaSource.asMap();
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
*
* @author Josh Cummings
* @author Sam Brannen
* @author DingHao
*/
final class AuthorizationAnnotationUtils {

Expand Down Expand Up @@ -107,6 +108,26 @@ private static <A extends Annotation> A findDistinctAnnotation(AnnotatedElement
};
}

static <A extends Annotation> MergedAnnotation<A> findUniqueMergedAnnotation(AnnotatedElement annotatedElement,
Class<A> annotationType) {
MergedAnnotations mergedAnnotations = MergedAnnotations.from(annotatedElement,
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());

List<MergedAnnotation<A>> annotations = mergedAnnotations.stream(annotationType)
.map(MergedAnnotation::withNonMergedAttributes)
.distinct()
.toList();

return switch (annotations.size()) {
case 0 -> null;
case 1 -> annotations.get(0);
default -> throw new AnnotationConfigurationException("""
Please ensure there is one unique annotation of type @%s attributed to %s. \
Found %d competing annotations: %s""".formatted(annotationType.getName(), annotatedElement,
annotations.size(), annotations));
};
}

private AuthorizationAnnotationUtils() {

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -18,10 +18,10 @@

import java.lang.reflect.Method;

import reactor.util.annotation.NonNull;

import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.MergedAnnotation;
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.prepost.PostAuthorize;
Expand All @@ -31,6 +31,7 @@
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @author DingHao
* @since 5.8
*/
final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
Expand All @@ -54,19 +55,21 @@ MethodSecurityExpressionHandler getExpressionHandler() {
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod);
MergedAnnotation<PostAuthorize> postAuthorize = findPostAuthorizeAnnotation(specificMethod);
if (postAuthorize == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression postAuthorizeExpression = this.expressionHandler.getExpressionParser()
.parseExpression(postAuthorize.value());
.parseExpression(postAuthorize.getString(MergedAnnotation.VALUE));
this.expressionHandler.setVariables(getMetaAnnotationAttribute(postAuthorize));
return new ExpressionAttribute(postAuthorizeExpression);
}

private PostAuthorize findPostAuthorizeAnnotation(Method method) {
PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostAuthorize.class);
return (postAuthorize != null) ? postAuthorize
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostAuthorize.class);
private MergedAnnotation<PostAuthorize> findPostAuthorizeAnnotation(Method method) {
MergedAnnotation<PostAuthorize> postAuthorize = AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method,
PostAuthorize.class);
return (postAuthorize != null) ? postAuthorize : AuthorizationAnnotationUtils
.findUniqueMergedAnnotation(method.getDeclaringClass(), PostAuthorize.class);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -19,6 +19,7 @@
import java.lang.reflect.Method;

import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.expression.Expression;
import org.springframework.lang.NonNull;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
Expand All @@ -30,6 +31,7 @@
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @author DingHao
* @since 5.8
*/
final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
Expand All @@ -53,19 +55,21 @@ MethodSecurityExpressionHandler getExpressionHandler() {
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PostFilter postFilter = findPostFilterAnnotation(specificMethod);
MergedAnnotation<PostFilter> postFilter = findPostFilterAnnotation(specificMethod);
if (postFilter == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression postFilterExpression = this.expressionHandler.getExpressionParser()
.parseExpression(postFilter.value());
.parseExpression(postFilter.getString(MergedAnnotation.VALUE));
this.expressionHandler.setVariables(getMetaAnnotationAttribute(postFilter));
return new ExpressionAttribute(postFilterExpression);
}

private PostFilter findPostFilterAnnotation(Method method) {
PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class);
private MergedAnnotation<PostFilter> findPostFilterAnnotation(Method method) {
MergedAnnotation<PostFilter> postFilter = AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method,
PostFilter.class);
return (postFilter != null) ? postFilter
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostFilter.class);
: AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method.getDeclaringClass(), PostFilter.class);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -18,10 +18,10 @@

import java.lang.reflect.Method;

import reactor.util.annotation.NonNull;

import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.MergedAnnotation;
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.prepost.PreAuthorize;
Expand All @@ -31,6 +31,7 @@
* For internal use only, as this contract is likely to change.
*
* @author Evgeniy Cheban
* @author DingHao
* @since 5.8
*/
final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
Expand Down Expand Up @@ -58,19 +59,21 @@ MethodSecurityExpressionHandler getExpressionHandler() {
@Override
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
MergedAnnotation<PreAuthorize> preAuthorize = findPreAuthorizeAnnotation(specificMethod);
if (preAuthorize == null) {
return ExpressionAttribute.NULL_ATTRIBUTE;
}
Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser()
.parseExpression(preAuthorize.value());
.parseExpression(preAuthorize.getString(MergedAnnotation.VALUE));
this.expressionHandler.setVariables(getMetaAnnotationAttribute(preAuthorize));
return new ExpressionAttribute(preAuthorizeExpression);
}

private PreAuthorize findPreAuthorizeAnnotation(Method method) {
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
return (preAuthorize != null) ? preAuthorize
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreAuthorize.class);
private MergedAnnotation<PreAuthorize> findPreAuthorizeAnnotation(Method method) {
MergedAnnotation<PreAuthorize> preAuthorize = AuthorizationAnnotationUtils.findUniqueMergedAnnotation(method,
PreAuthorize.class);
return (preAuthorize != null) ? preAuthorize : AuthorizationAnnotationUtils
.findUniqueMergedAnnotation(method.getDeclaringClass(), PreAuthorize.class);
}

}
Loading
Loading