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 stream = Stream.of("1", "2", "3"); + + Expression expression = handler.getExpressionParser().parseExpression("filterObject ne '2'"); + + EvaluationContext context = handler.createEvaluationContext(authentication, + methodInvocation); + + Object filtered = handler.filter(stream, expression, context); + + Assert.assertTrue("response was wrong type", Stream.class.isAssignableFrom(filtered.getClass())); + List list = ((Stream) filtered).collect(Collectors.toList()); + Assert.assertEquals(2, list.size()); + Assert.assertFalse("contains filtered element", list.contains("2")); + } }