core icon indicating copy to clipboard operation
core copied to clipboard

When event_listeners_backward_compatibility_layer is disabled I can't pass URI variables into a Voter in security property.

Open nickkadutskyi opened this issue 9 months ago • 3 comments

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

  1. Have some resource with uriTemplate
  2. Set event_listeners_backward_compatibility_layer to false
  3. Pass uriTemplate to a voter in security property
  4. 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)         

nickkadutskyi avatar Nov 10 '23 05:11 nickkadutskyi

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'))"

codedge avatar Nov 24 '23 10:11 codedge

Definitely a bug, good catch. I'll see for this to be resolved and tested.

soyuka avatar Nov 24 '23 13:11 soyuka

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.

mariantrifu avatar Mar 12 '24 12:03 mariantrifu