When event_listeners_backward_compatibility_layer is disabled I can't pass URI variables into a Voter in security property.
API Platform version(s) affected: 3.2.3
Description
I have a resource where I pass a URI variable into a voter. But when I set event_listeners_backward_compatibility_layer to false and request GET /users/{user_id}/stats I get this error:
Variable "user_id" is not valid around position 87 for expression `is_granted('ROLE_ADMIN') or is_granted('ROLE_USER') and is_granted('USER_STATS_VIEW', user_id)`.
How to reproduce
- Have some resource with uriTemplate
- Set
event_listeners_backward_compatibility_layertofalse - Pass uriTemplate to a voter in security property
- Get the error
Possible Solution
I think it has something to do with what providers are called after $uriVariables = $this->getOperationUriVariables... in ReadListener.php and in MainController.php
Additional Context
I do not know if that's by design so I kept event_listeners_backward_compatibility_layer set to false and now pass request variable to Voter and get user_id from it and this works fine.
Resource config for the original issue:
...
#[ApiResource(
shortName: 'Stat',
operations: [
new Get(
uriTemplate: '/users/{user_id}/stats/{id}.{_format}',
uriVariables: [
'user_id' => new Link(
toProperty: 'user',
fromClass: User::class,
),
'id' => new Link(
fromProperty: 'id',
fromClass: Stat::class
),
],
),
new GetCollection(
uriTemplate: '/users/{user_id}/stats.{_format}',
uriVariables: [
'user_id' => new Link(fromClass: User::class),
],
)
],
security: "is_granted('ROLE_ADMIN') or is_granted('ROLE_USER') and is_granted('USER_STATS_VIEW', user_id)",
provider: StatsProvider::class
)]
class Stat
{
...
Stack Trace:
Symfony\Component\ExpressionLanguage\SyntaxError:
Variable "user_id" is not valid around position 87 for expression `is_granted('ROLE_ADMIN') or is_granted('ROLE_USER') and is_granted('USER_STATS_VIEW', user_id)`.
at vendor/symfony/expression-language/Parser.php:249
at Symfony\Component\ExpressionLanguage\Parser->parsePrimaryExpression()
(vendor/symfony/expression-language/Parser.php:180)
at Symfony\Component\ExpressionLanguage\Parser->getPrimary()
(vendor/symfony/expression-language/Parser.php:138)
at Symfony\Component\ExpressionLanguage\Parser->parseExpression()
(vendor/symfony/expression-language/Parser.php:433)
at Symfony\Component\ExpressionLanguage\Parser->parseArguments()
(vendor/symfony/expression-language/Parser.php:245)
at Symfony\Component\ExpressionLanguage\Parser->parsePrimaryExpression()
(vendor/symfony/expression-language/Parser.php:180)
at Symfony\Component\ExpressionLanguage\Parser->getPrimary()
(vendor/symfony/expression-language/Parser.php:138)
at Symfony\Component\ExpressionLanguage\Parser->parseExpression(16)
(vendor/symfony/expression-language/Parser.php:144)
at Symfony\Component\ExpressionLanguage\Parser->parseExpression(11)
(vendor/symfony/expression-language/Parser.php:144)
at Symfony\Component\ExpressionLanguage\Parser->parseExpression()
(vendor/symfony/expression-language/Parser.php:123)
at Symfony\Component\ExpressionLanguage\Parser->doParse(object(TokenStream), array('auth_checker', 'object', 'previous_object', 'request', 'roles', 'token', 'trust_resolver', 'user'))
(vendor/symfony/expression-language/Parser.php:98)
at Symfony\Component\ExpressionLanguage\Parser->parse(object(TokenStream), array('auth_checker', 'object', 'previous_object', 'request', 'roles', 'token', 'trust_resolver', 'user'))
(vendor/symfony/expression-language/ExpressionLanguage.php:81)
at Symfony\Component\ExpressionLanguage\ExpressionLanguage->parse('is_granted(\'ROLE_ADMIN\') or is_granted(\'ROLE_USER\') and is_granted(\'USER_STATS_VIEW\', user_id)', array('auth_checker', 'object', 'previous_object', 'request', 'roles', 'token', 'trust_resolver', 'user'))
(vendor/symfony/expression-language/ExpressionLanguage.php:59)
at Symfony\Component\ExpressionLanguage\ExpressionLanguage->evaluate('is_granted(\'ROLE_ADMIN\') or is_granted(\'ROLE_USER\') and is_granted(\'USER_STATS_VIEW\', user_id)', array('object' => array(object(Stat)), 'previous_object' => array(object(Stat)), 'request' => object(Request), 'trust_resolver' => object(AuthenticationTrustResolver), 'auth_checker' => object(AuthorizationChecker), 'token' => object(UsernamePasswordToken), 'user' => object(User), 'roles' => array('ROLE_ADMIN', 'ROLE_USER')))
(vendor/api-platform/core/src/Symfony/Security/ResourceAccessChecker.php:56)
at ApiPlatform\Symfony\Security\ResourceAccessChecker->isGranted('App\\ApiResource\\Stat', 'is_granted(\'ROLE_ADMIN\') or is_granted(\'ROLE_USER\') and is_granted(\'USER_STATS_VIEW\', user_id)', array('object' => array(object(Stat)), 'previous_object' => array(object(Stat)), 'request' => object(Request)))
(vendor/api-platform/core/src/Symfony/Security/State/AccessCheckerProvider.php:78)
at ApiPlatform\Symfony\Security\State\AccessCheckerProvider->provide(object(GetCollection), array('user_id' => 1), array('request' => object(Request), 'uri_variables' => array('user_id' => 1), 'resource_class' => 'App\\ApiResource\\Stat'))
(vendor/api-platform/core/src/State/Provider/DeserializeProvider.php:47)
at ApiPlatform\State\Provider\DeserializeProvider->provide(object(GetCollection), array('user_id' => 1), array('request' => object(Request), 'uri_variables' => array('user_id' => 1), 'resource_class' => 'App\\ApiResource\\Stat'))
(vendor/api-platform/core/src/Symfony/Security/State/AccessCheckerProvider.php:53)
at ApiPlatform\Symfony\Security\State\AccessCheckerProvider->provide(object(GetCollection), array('user_id' => 1), array('request' => object(Request), 'uri_variables' => array('user_id' => 1), 'resource_class' => 'App\\ApiResource\\Stat'))
(vendor/api-platform/core/src/Symfony/Validator/State/QueryParameterValidateProvider.php:54)
at ApiPlatform\Symfony\Validator\State\QueryParameterValidateProvider->provide(object(GetCollection), array('user_id' => 1), array('request' => object(Request), 'uri_variables' => array('user_id' => 1), 'resource_class' => 'App\\ApiResource\\Stat'))
(vendor/api-platform/core/src/Symfony/Validator/State/ValidateProvider.php:32)
at ApiPlatform\Symfony\Validator\State\ValidateProvider->provide(object(GetCollection), array('user_id' => 1), array('request' => object(Request), 'uri_variables' => array('user_id' => 1), 'resource_class' => 'App\\ApiResource\\Stat'))
(vendor/api-platform/core/src/Symfony/Security/State/AccessCheckerProvider.php:53)
at ApiPlatform\Symfony\Security\State\AccessCheckerProvider->provide(object(GetCollection), array('user_id' => 1), array('request' => object(Request), 'uri_variables' => array('user_id' => 1), 'resource_class' => 'App\\ApiResource\\Stat'))
(vendor/api-platform/core/src/State/Provider/ContentNegotiationProvider.php:56)
at ApiPlatform\State\Provider\ContentNegotiationProvider->provide(object(GetCollection), array('user_id' => 1), array('request' => object(Request), 'uri_variables' => array('user_id' => 1), 'resource_class' => 'App\\ApiResource\\Stat'))
(vendor/api-platform/core/src/Symfony/Controller/MainController.php:74)
at ApiPlatform\Symfony\Controller\MainController->__invoke(object(Request))
(vendor/symfony/http-kernel/HttpKernel.php:181)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(vendor/symfony/http-kernel/HttpKernel.php:76)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor/symfony/http-kernel/Kernel.php:197)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php:35)
at Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner->run()
(vendor/autoload_runtime.php:29)
at require_once('/Users/nick/Developer/UPWZ/0000/job-search-api/vendor/autoload_runtime.php')
(public/index.php:5)
Additionally, values like _api_resource_class can also not be used anymore and produce the same error as above.
Update
I was able to work around my problem using request.get('_api_resource_class') instead of _api_resource_class only.
security: "is_granted('custom:format', request.get('_api_resource_class'))"
Definitely a bug, good catch. I'll see for this to be resolved and tested.
Hi, any news on this? It looks like it still persist on version API Platform 3.2.16. Or please help me with a work around.
L.E. the above approach with request.get('id') seems to work.