I did not know whether to log this as a bug or an enhancement, but it feels more like a bug to me. Either in the migration documentation or in code. :-)

Describe the bug Given 2 AuthorizationManagers/DecisionVoters: - AffirmativeBased way of working denies access when the first DecisionVoterabstains and the second denies. - AuthorizationManagers.anyOf(...) allows access when the first AuthorizationManagerabstains.

To Reproduce I believe there is a small difference in the implementation between AffirmativeBasedand AuthorizationManagers.anyOf(). More precisely in handling AccessDecisionVoter.ABSTAIN vs a nullinstance of AuthorizationDecision.

AffirmativeBased.java

public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException {
    int deny = 0;
    for (AccessDecisionVoter voter : getDecisionVoters()) {
        int result = voter.vote(authentication, object, configAttributes);
        switch (result) {
        case AccessDecisionVoter.ACCESS_GRANTED:
            return;
        case AccessDecisionVoter.ACCESS_DENIED:
            deny++;
            break;
        default:
            break; // --> Continues the loop and checks all DecisionVoters.
        }
    }
    if (deny > 0) {
        throw new AccessDeniedException(
                this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
    }
    // To get this far, every AccessDecisionVoter abstained
    checkAllowIfAllAbstainDecisions();
}

AuthorizationManagers.java

public static <T> AuthorizationManager<T> anyOf(AuthorizationManager<T>... managers) {
    return (authentication, object) -> {
        List<AuthorizationDecision> decisions = new ArrayList<>();
        for (AuthorizationManager<T> manager : managers) {
            AuthorizationDecision decision = manager.check(authentication, object);
            if (decision == null || decision.isGranted()) {
                return decision; // --> Shortcuts the whole decision making process. Basically says ABSTAIN == GRANTED
            }
            decisions.add(decision);
        }
        return new CompositeAuthorizationDecision(false, decisions);
    };
}

Expected behavior The behavior should be the same, or the migration docs should state that the implementations are not equivalent.

Comment From: jzheaux

Hi, @KaVeKal. I agree that this is a bug in AuthorizationManagers#anyOf, but I also see a bug in the docs related to allOf. anyOf should return true only if any of the non-abstaining delegates return true. allOf should return true only if all of the non-abstaining delegates return true.

I'll update the code as well as the docs accordingly.

Also, I'm thinking that it would be good to introduce a configuration option to clarify what should be done when all delegates abstain. I've created #13085 to address that.