mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-03 17:22:13 +00:00
Adding Map support to DefaultMethodSecurityExpressionHandler
Closes gh-8331
This commit is contained in:
commit
b6fb063145
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,7 +19,9 @@ import java.lang.reflect.Array;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.*;
|
import java.util.stream.*;
|
||||||
|
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
@ -87,10 +89,10 @@ public class DefaultMethodSecurityExpressionHandler extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the {@code filterTarget} object (which must be either a collection, array,
|
* Filters the {@code filterTarget} object (which must be either a collection, array, map
|
||||||
* or stream), by evaluating the supplied expression.
|
* or stream), by evaluating the supplied expression.
|
||||||
* <p>
|
* <p>
|
||||||
* If a {@code Collection} is used, the original instance will be modified to contain
|
* If a {@code Collection} or {@code Map} is used, the original instance will be modified to contain
|
||||||
* the elements for which the permission expression evaluates to {@code true}. For an
|
* the elements for which the permission expression evaluates to {@code true}. For an
|
||||||
* array, a new array instance will be returned.
|
* array, a new array instance will be returned.
|
||||||
*/
|
*/
|
||||||
@ -173,6 +175,32 @@ public class DefaultMethodSecurityExpressionHandler extends
|
|||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filterTarget instanceof Map) {
|
||||||
|
final Map<?, ?> map = (Map<?, ?>) filterTarget;
|
||||||
|
final Map retainMap = new LinkedHashMap(map.size());
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
logger.debug("Filtering map with " + map.size() + " elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<?, ?> filterObject : map.entrySet()) {
|
||||||
|
rootObject.setFilterObject(filterObject);
|
||||||
|
|
||||||
|
if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
|
||||||
|
retainMap.put(filterObject.getKey(), filterObject.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
logger.debug("Retaining elements: " + retainMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.clear();
|
||||||
|
map.putAll(retainMap);
|
||||||
|
|
||||||
|
return filterTarget;
|
||||||
|
}
|
||||||
|
|
||||||
if (filterTarget instanceof Stream) {
|
if (filterTarget instanceof Stream) {
|
||||||
final Stream<?> original = (Stream<?>) filterTarget;
|
final Stream<?> original = (Stream<?>) filterTarget;
|
||||||
|
|
||||||
@ -184,7 +212,7 @@ public class DefaultMethodSecurityExpressionHandler extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Filter target must be a collection, array, or stream type, but was "
|
"Filter target must be a collection, array, map or stream type, but was "
|
||||||
+ filterTarget);
|
+ filterTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.access.expression.method;
|
package org.springframework.security.access.expression.method;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@ -78,6 +80,72 @@ public class DefaultMethodSecurityExpressionHandlerTests {
|
|||||||
verify(trustResolver).isAnonymous(authentication);
|
verify(trustResolver).isAnonymous(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void filterByKeyWhenUsingMapThenFiltersMap() {
|
||||||
|
final Map<String, String> map = new HashMap<>();
|
||||||
|
map.put("key1", "value1");
|
||||||
|
map.put("key2", "value2");
|
||||||
|
map.put("key3", "value3");
|
||||||
|
|
||||||
|
Expression expression = handler.getExpressionParser().parseExpression("filterObject.key eq 'key2'");
|
||||||
|
|
||||||
|
EvaluationContext context = handler.createEvaluationContext(authentication,
|
||||||
|
methodInvocation);
|
||||||
|
|
||||||
|
Object filtered = handler.filter(map, expression, context);
|
||||||
|
|
||||||
|
assertThat(filtered == map);
|
||||||
|
Map<String, String> result = ((Map<String, String>) filtered);
|
||||||
|
assertThat(result.size() == 1);
|
||||||
|
assertThat(result).containsKey("key2");
|
||||||
|
assertThat(result).containsValue("value2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void filterByValueWhenUsingMapThenFiltersMap() {
|
||||||
|
final Map<String, String> map = new HashMap<>();
|
||||||
|
map.put("key1", "value1");
|
||||||
|
map.put("key2", "value2");
|
||||||
|
map.put("key3", "value3");
|
||||||
|
|
||||||
|
Expression expression = handler.getExpressionParser().parseExpression("filterObject.value eq 'value3'");
|
||||||
|
|
||||||
|
EvaluationContext context = handler.createEvaluationContext(authentication,
|
||||||
|
methodInvocation);
|
||||||
|
|
||||||
|
Object filtered = handler.filter(map, expression, context);
|
||||||
|
|
||||||
|
assertThat(filtered == map);
|
||||||
|
Map<String, String> result = ((Map<String, String>) filtered);
|
||||||
|
assertThat(result.size() == 1);
|
||||||
|
assertThat(result).containsKey("key3");
|
||||||
|
assertThat(result).containsValue("value3");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void filterByKeyAndValueWhenUsingMapThenFiltersMap() {
|
||||||
|
final Map<String, String> map = new HashMap<>();
|
||||||
|
map.put("key1", "value1");
|
||||||
|
map.put("key2", "value2");
|
||||||
|
map.put("key3", "value3");
|
||||||
|
|
||||||
|
Expression expression = handler.getExpressionParser().parseExpression("(filterObject.key eq 'key1') or (filterObject.value eq 'value2')");
|
||||||
|
|
||||||
|
EvaluationContext context = handler.createEvaluationContext(authentication,
|
||||||
|
methodInvocation);
|
||||||
|
|
||||||
|
Object filtered = handler.filter(map, expression, context);
|
||||||
|
|
||||||
|
assertThat(filtered == map);
|
||||||
|
Map<String, String> result = ((Map<String, String>) filtered);
|
||||||
|
assertThat(result.size() == 2);
|
||||||
|
assertThat(result).containsKeys("key1", "key2");
|
||||||
|
assertThat(result).containsValues("value1", "value2");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void filterWhenUsingStreamThenFiltersStream() {
|
public void filterWhenUsingStreamThenFiltersStream() {
|
||||||
|
@ -304,7 +304,7 @@ To access the return value from a method, use the built-in name `returnObject` i
|
|||||||
--
|
--
|
||||||
|
|
||||||
===== Filtering using @PreFilter and @PostFilter
|
===== Filtering using @PreFilter and @PostFilter
|
||||||
As you may already be aware, Spring Security supports filtering of collections and arrays and this can now be achieved using expressions.
|
Spring Security supports filtering of collections, arrays, maps and streams using expressions.
|
||||||
This is most commonly performed on the return value of a method.
|
This is most commonly performed on the return value of a method.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@ -315,8 +315,10 @@ For example:
|
|||||||
public List<Contact> getAll();
|
public List<Contact> getAll();
|
||||||
----
|
----
|
||||||
|
|
||||||
When using the `@PostFilter` annotation, Spring Security iterates through the returned collection and removes any elements for which the supplied expression is false.
|
When using the `@PostFilter` annotation, Spring Security iterates through the returned collection or map and removes any elements for which the supplied expression is false.
|
||||||
|
For an array, a new array instance will be returned containing filtered elements.
|
||||||
The name `filterObject` refers to the current object in the collection.
|
The name `filterObject` refers to the current object in the collection.
|
||||||
|
In case when a map is used it will refer to the current `Map.Entry` object which allows one to use `filterObject.key` or `filterObject.value` in the expresion.
|
||||||
You can also filter before the method call, using `@PreFilter`, though this is a less common requirement.
|
You can also filter before the method call, using `@PreFilter`, though this is a less common requirement.
|
||||||
The syntax is just the same, but if there is more than one argument which is a collection type then you have to select one by name using the `filterTarget` property of this annotation.
|
The syntax is just the same, but if there is more than one argument which is a collection type then you have to select one by name using the `filterTarget` property of this annotation.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user