[Security] Support voter reasons in the "access denied" responses
Description
Symfony (since 7.3) supports Voters setting a "reason" for an access decision: https://github.com/symfony/symfony/pull/59771
Please implement an option to output the reason(s) given by the Voter(s) when access to an endpoint is denied.
Example
ApiResource on the User entity with security property:
new Delete(
security: "is_granted('DELETE', object)",
),
UserVoter:
case self::DELETE:
if (null !== $subject->getAccount()) {
$vote?->addReason('User has an associated account and cannot be deleted');
return false;
}
Possible problem response:
{
detail: "User has an associated account and cannot be deleted",
status: 403,
title: "An error occurred",
type: "/errors/403",
}
Or instead of modifying the detail, return a separate field that may contain a list of reasons, not just the last reason.
Details
APIP currently cannot output this reason when it throws an AccessDenied[Http]Exception in the AccessCheckerProvider because its ResourceAccessChecker uses the ExpressionLanguage, which currently has no support for specifying an AccessDecision object like Symfony\Component\Security\Core\Authorization\AuthorizationChecker::isGranted has, that could later be evaluated for the reasons.
APIP's ResourceAccessChecker has its own $authorizationChecker, which could be accessed after evaluating the expression. This $authorizationChecker has an $accessDecisionStack, which is sadly empty and private. But it also has a (private) $accessDecisionManager, which again has an $accessDecisionStack. And this stack has the AccessDecision object with all votes and reasons, but I have no idea how APIP could access this. I hope someone has a better idea than overwriting all those classes/services with custom versions to allow access to those fields.