diff --git a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java index 9c37a1b4126..cda0beead2c 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java @@ -129,7 +129,7 @@ private MethodSecurityExpressionOperations createSecurityExpressionRoot( * {@link Stream} */ @Override - public Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx) { + public @Nullable Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx) { MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx.getRootObject() .getValue(); Assert.notNull(rootObject, "rootObject cannot be null"); diff --git a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionHandler.java index 1afe7c25dda..19ceb940bb6 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityExpressionHandler.java @@ -40,9 +40,9 @@ public interface MethodSecurityExpressionHandler extends SecurityExpressionHandl * returned collection * @param ctx the current evaluation context (as created through a call to * {@link #createEvaluationContext(org.springframework.security.core.Authentication, Object)} - * @return the filtered collection or array + * @return the filtered collection or array, or {@code null} */ - Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx); + @Nullable Object filter(@Nullable Object filterTarget, Expression filterExpression, EvaluationContext ctx); /** * Used to inform the expression system of the return object for the given evaluation diff --git a/core/src/test/kotlin/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerKotlinTests.kt b/core/src/test/kotlin/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerKotlinTests.kt index f766c6aea01..1c16810a1d8 100644 --- a/core/src/test/kotlin/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerKotlinTests.kt +++ b/core/src/test/kotlin/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerKotlinTests.kt @@ -66,7 +66,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { "key3" to "value3", ) - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ nonEmptyMap, /* filterExpression = */ expression, /* ctx = */ context, @@ -89,7 +89,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { ) val emptyMap: Map = emptyMap() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ emptyMap, /* filterExpression = */ expression, /* ctx = */ context, @@ -114,7 +114,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { "string1", ) - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ nonEmptyCollection, /* filterExpression = */ expression, /* ctx = */ context, @@ -136,7 +136,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { ) val emptyCollection: Collection = emptyList() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ emptyCollection, /* filterExpression = */ expression, /* ctx = */ context, @@ -161,7 +161,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { "string1", ) - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ nonEmptyArray, /* filterExpression = */ expression, /* ctx = */ context, @@ -183,7 +183,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { ) val emptyArray: Array = emptyArray() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ emptyArray, /* filterExpression = */ expression, /* ctx = */ context, @@ -208,7 +208,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { "string1", ).stream() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ nonEmptyStream, /* filterExpression = */ expression, /* ctx = */ context, @@ -230,7 +230,7 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { ) val emptyStream: Stream = emptyList().stream() - val filtered: Any = handler.filter( + val filtered: Any? = handler.filter( /* filterTarget = */ emptyStream, /* filterExpression = */ expression, /* ctx = */ context, @@ -241,4 +241,20 @@ class DefaultMethodSecurityExpressionHandlerKotlinTests { val result = (filtered as Stream).toList() assertThat(result).hasSize(0) } + + @Test + fun `allows null filter results`() { + val handler = NullReturningMethodSecurityExpressionHandler() + val expression: Expression = handler.expressionParser.parseExpression("filterObject eq 'string2'") + val context: EvaluationContext = handler.createEvaluationContext( + /* authentication = */ authentication, + /* invocation = */ methodInvocation, + ) + + assertThat(handler.filter(null, expression, context)).isNull() + } + + private class NullReturningMethodSecurityExpressionHandler : DefaultMethodSecurityExpressionHandler() { + override fun filter(filterTarget: Any?, filterExpression: Expression, ctx: EvaluationContext): Any? = null + } }