Adding Map support to DefaultMethodSecurityExpressionHandler

This commit is contained in:
Maksim Mednik 2020-04-04 15:46:07 -04:00
parent dc6b8ce470
commit eacd212a5a
2 changed files with 100 additions and 4 deletions

View File

@ -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);
} }

View File

@ -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() {