This is not a bug but an improvement suggestion.

I'd like to use @postfilter annotation to filter data in a collection based on security roles. My problem is that the controller returns a HashMap which contains the collection HashMap<String, List<MyObj>>

By using the @postfilter I get the following exception: java.lang.IllegalArgumentException: Filter target must be a collection or array type, but was {result=[myObj: []]} because the return type is the HashMap containing the list of MyObj for the key result This is due to the check in the DefaultMethodSecurityExpressionHandler.filter which checks if the filterTarget is a collection or array.

The best would be to have the opportunity to specify in the SPEL expression the referred object containing the collection.

I'm using Spring Security 3.2.9.RELEASE

Comment From: maxtacco

Hi, any updates on this? May be support Maps at least?

Comment From: maxtacco

@gcorsaro Thanks a lot! Cheers!

Comment From: gcorsaro

I solved by extending the DefaultMethodSecurityExpressionHandler and overwriting the filter method in this way:

@Override
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
    //currently only Maps and ImmutableMaps are supported
    if (ImmutableMap.class.isAssignableFrom(filterTarget.getClass())) {
        return ImmutableMap.builder().putAll(rebuildMap(Maps.newHashMap((ImmutableMap) filterTarget), filterExpression, ctx)).build();
    } else if (Map.class.isAssignableFrom(filterTarget.getClass())) {    
        return rebuildMap((Map) filterTarget, filterExpression, ctx);
    }
    // normal behaviour
    return super.filter(filterTarget, filterExpression, ctx);

}
private Map rebuildMap(Map map, Expression filterExpression, EvaluationContext ctx) {
    for (Object value : map.keySet()) {
        if (map.get(value) instanceof Collection) { // HashMap with collection value
            map.put(value, super.filter(map.get(value), filterExpression, ctx));
        }
        if (map.get(value) instanceof Boolean) { 
            // HashMap with boolean value. 
            // Both the values coming from the business rules and from the authorisation must be true, otherwise returns false
            map.put(value, ((Boolean)map.get(value)) && ExpressionUtils.evaluateAsBoolean(filterExpression, ctx));
        }
    }
    return map;
}

However it would be nice if it was supported directly by spring

Comment From: rwinch

I think there is ambiguity in what it is filtering when a Map is returned. Is it operating on keys, values, both?

Comment From: maxtacco

@rwinch Hi, I stumbled on that too. In my case though I was more interested in filtering by keys. May be filtering on Map.Entry can work? Then in theory one could use filterObject.key or filterObject.value?

Comment From: rwinch

I think that would be a viable option. What do you think about submitting a PR to support filtering on Map.Entry?

Comment From: maxtacco

@rwinch I'm quite busy right now at work. But, I hope I have some time later this month and I'll try to create a PR. I'll update this issue if I have any road blocks either way.

Comment From: gcorsaro

I think there is ambiguity in what it is filtering when a Map is returned. Is it operating on keys, values, both?

In this case it was operating just on values supporting boolean and collections only (I needed just that one). But of course it can be extended according to own needs.

Comment From: maxtacco

@rwinch Hey, I actually had some time today :) Here is the pull request: https://github.com/spring-projects/spring-security/pull/8331

Comment From: gcorsaro

@rwinch Hey, I actually had some time today :) Here is the pull request: #8331

Hi @maxtacco, I gave a look to your commit but I didn't understand very well the snippet

                      for (Map.Entry<?, ?> filterObject : map.entrySet()) {
                rootObject.setFilterObject(filterObject);

                if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
                    retainMap.put(filterObject.getKey(), filterObject.getValue());
                }
            }

It looks like you treat only values of boolean type. Am I wrong?

Comment From: maxtacco

@gcorsaro Hey, the expression must evaluate to boolean. Also, I set Map.Entry as a filterObject so that each map element can be accessed by filterObject.key or filterObject.value. Take a look at the test changes here https://github.com/spring-projects/spring-security/pull/8331/files#diff-53c9d89898e1fdf20b9b1cc5b979b2d7

Comment From: gcorsaro

Yes, got it. Indeed this is different than what I did in my case. I needed to change map values based on some rules. In your case you're filtering the map without changing its values.