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 051618e60b..fbac0af8f1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.stream.*; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; @@ -86,8 +87,8 @@ public class DefaultMethodSecurityExpressionHandler extends } /** - * Filters the {@code filterTarget} object (which must be either a collection or an - * array), by evaluating the supplied expression. + * Filters the {@code filterTarget} object (which must be either a collection, array, + * or stream), by evaluating the supplied expression. *
* If a {@code Collection} is used, the original instance will be modified to contain
* the elements for which the permission expression evaluates to {@code true}. For an
@@ -172,8 +173,27 @@ public class DefaultMethodSecurityExpressionHandler extends
return filtered;
}
+ if (filterTarget instanceof Stream) {
+ final Stream> original = (Stream>) filterTarget;
+ if (debug) {
+ logger.debug("Filtering stream with " + original.count() + " elements");
+ }
+
+ Stream> filtered = original.filter(filterObject -> {
+ rootObject.setFilterObject(filterObject);
+ return ExpressionUtils.evaluateAsBoolean(filterExpression, ctx);
+ })
+ .onClose(original::close);
+
+ if (debug) {
+ logger.debug("Retaining elements: " + filtered.collect(Collectors.toList()));
+ }
+
+ return filtered;
+ }
+
throw new IllegalArgumentException(
- "Filter target must be a collection or array type, but was "
+ "Filter target must be a collection, array, or stream type, but was "
+ filterTarget);
}
diff --git a/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java b/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java
index 855ed2da8d..b7ff6377bf 100644
--- a/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java
+++ b/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2018 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.
@@ -19,6 +19,7 @@ import static org.mockito.Mockito.verify;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,6 +31,9 @@ import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
+import java.util.List;
+import java.util.stream.*;
+
@RunWith(MockitoJUnitRunner.class)
public class DefaultMethodSecurityExpressionHandlerTests {
private DefaultMethodSecurityExpressionHandler handler;
@@ -68,4 +72,22 @@ public class DefaultMethodSecurityExpressionHandlerTests {
verify(trustResolver).isAnonymous(authentication);
}
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testFilteringStream() {
+ final Stream