1515
1616use ApiPlatform \Metadata \ResourceAccessCheckerInterface ;
1717use Symfony \Component \ExpressionLanguage \ExpressionLanguage ;
18+ use Symfony \Component \ExpressionLanguage \Node \NameNode ;
19+ use Symfony \Component \ExpressionLanguage \Node \Node ;
1820use Symfony \Component \Security \Core \Authentication \AuthenticationTrustResolverInterface ;
1921use Symfony \Component \Security \Core \Authentication \Token \NullToken ;
2022use Symfony \Component \Security \Core \Authentication \Token \Storage \TokenStorageInterface ;
2729 *
2830 * @author Kévin Dunglas <dunglas@gmail.com>
2931 */
30- final class ResourceAccessChecker implements ResourceAccessCheckerInterface
32+ final class ResourceAccessChecker implements ResourceAccessCheckerInterface, ObjectVariableCheckerInterface
3133{
3234 public function __construct (private readonly ?ExpressionLanguage $ expressionLanguage = null , private readonly ?AuthenticationTrustResolverInterface $ authenticationTrustResolver = null , private readonly ?RoleHierarchyInterface $ roleHierarchy = null , private readonly ?TokenStorageInterface $ tokenStorage = null , private readonly ?AuthorizationCheckerInterface $ authorizationChecker = null )
3335 {
@@ -43,32 +45,32 @@ public function isGranted(string $resourceClass, string $expression, array $extr
4345 throw new \LogicException ('The "symfony/expression-language" library must be installed to use the "security" attribute. ' );
4446 }
4547
46- $ variables = array_merge ($ extraVariables , [
47- 'trust_resolver ' => $ this ->authenticationTrustResolver ,
48- 'auth_checker ' => $ this ->authorizationChecker , // needed for the is_granted expression function
49- ]);
50-
51- if (null === $ token = $ this ->tokenStorage ->getToken ()) {
52- $ token = new NullToken ();
53- }
54-
55- $ variables = array_merge ($ variables , $ this ->getVariables ($ token ));
48+ return (bool ) $ this ->expressionLanguage ->evaluate ($ expression , $ this ->getVariables ($ extraVariables ));
49+ }
5650
57- return (bool ) $ this ->expressionLanguage ->evaluate ($ expression , $ variables );
51+ public function usesObjectVariable (string $ expression , array $ variables = []): bool
52+ {
53+ return $ this ->hasObjectVariable ($ this ->expressionLanguage ->parse ($ expression , array_keys ($ this ->getVariables ($ variables )))->getNodes ()->toArray ());
5854 }
5955
6056 /**
6157 * @copyright Fabien Potencier <fabien@symfony.com>
6258 *
6359 * @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php
6460 */
65- private function getVariables (TokenInterface $ token ): array
61+ private function getVariables (array $ variables ): array
6662 {
67- return [
63+ if (null === $ token = $ this ->tokenStorage ->getToken ()) {
64+ $ token = new NullToken ();
65+ }
66+
67+ return array_merge ($ variables , [
6868 'token ' => $ token ,
6969 'user ' => $ token ->getUser (),
7070 'roles ' => $ this ->getEffectiveRoles ($ token ),
71- ];
71+ 'trust_resolver ' => $ this ->authenticationTrustResolver ,
72+ 'auth_checker ' => $ this ->authorizationChecker , // needed for the is_granted expression function
73+ ]);
7274 }
7375
7476 /**
@@ -82,4 +84,44 @@ private function getEffectiveRoles(TokenInterface $token): array
8284
8385 return $ this ->roleHierarchy ->getReachableRoleNames ($ token ->getRoleNames ());
8486 }
87+
88+ /**
89+ * Recursively checks if a variable named 'object' is present in the expression AST.
90+ *
91+ * @param Node|array<mixed>|null $nodeOrNodes the ExpressionLanguage Node instance or an array of nodes/values
92+ */
93+ private function hasObjectVariable (Node |array |null $ nodeOrNodes ): bool
94+ {
95+ if ($ nodeOrNodes instanceof NameNode) {
96+ if ('object ' === $ nodeOrNodes ->attributes ['name ' ] || 'previous_object ' === $ nodeOrNodes ->attributes ['name ' ]) {
97+ return true ;
98+ }
99+
100+ return false ;
101+ }
102+
103+ if ($ nodeOrNodes instanceof Node) {
104+ foreach ($ nodeOrNodes ->nodes as $ childNode ) {
105+ if ($ this ->hasObjectVariable ($ childNode )) {
106+ return true ;
107+ }
108+ }
109+
110+ return false ;
111+ }
112+
113+ if (\is_array ($ nodeOrNodes )) {
114+ foreach ($ nodeOrNodes as $ element ) {
115+ if (\is_string ($ element )) {
116+ continue ;
117+ }
118+
119+ if ($ this ->hasObjectVariable ($ element )) {
120+ return true ;
121+ }
122+ }
123+ }
124+
125+ return false ;
126+ }
85127}
0 commit comments