Browse Source

PostFilter Support for Streams

Users can return a Stream from a @PostFilter-annotated method.

Fixes: gh-3743
Karl Goffin 6 years ago
parent
commit
92e68a589a

+ 24 - 4
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.
 	 * <p>
 	 * 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);
 	}
 

+ 23 - 1
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<String> 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<String> list = ((Stream<String>) filtered).collect(Collectors.toList());
+		Assert.assertEquals(2, list.size());
+		Assert.assertFalse("contains filtered element", list.contains("2"));
+	}
 }