Skip to content

Commit

Permalink
security meta-annotation support allowed for parameters.
Browse files Browse the repository at this point in the history
Closes gh-14480
  • Loading branch information
kse-music committed Jan 25, 2024
1 parent bdc0bd6 commit eda7acb
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 33 deletions.
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 (variables != null) {
setVariables(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 @@ -22,11 +22,14 @@
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.SecurityExpressionHandler;

import java.util.Map;

/**
* Extended expression-handler facade which adds methods which are specific to securing
* 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,6 +18,7 @@

import java.lang.reflect.Method;

import org.springframework.core.annotation.MergedAnnotation;
import reactor.util.annotation.NonNull;

import org.springframework.aop.support.AopUtils;
Expand All @@ -31,6 +32,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 +56,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 @@ -17,7 +17,9 @@
package org.springframework.security.authorization.method;

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

import org.springframework.core.annotation.MergedAnnotation;
import reactor.util.annotation.NonNull;

import org.springframework.aop.support.AopUtils;
Expand All @@ -31,6 +33,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 +61,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

0 comments on commit eda7acb

Please sign in to comment.