Core: Kernel view event never triggered on custom operation

Created on 30 Oct 2018  路  8Comments  路  Source: api-platform/core

I have this subscriber used to check right on each API operation:

use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

final class ApiAccessSubscriber implements EventSubscriberInterface
{
    /**
     * @var AuthorizationCheckerInterface
     */
    private $authorizationChecker;

    public function __construct(AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->authorizationChecker = $authorizationChecker;
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::VIEW => ['checkAccess', EventPriorities::PRE_VALIDATE],
        ];
    }

    public function checkAccess(GetResponseForControllerResultEvent $event): void
    {
        $request = $event->getRequest();

        if (!$request->attributes->has('_api_collection_operation_name')
            &&
            !$request->attributes->has('_api_item_operation_name')
        ) {
            return;
        }

        $isCollection = $request->attributes->has('_api_collection_operation_name');
        $operationType = $isCollection ? 'collection' : 'item';

        if (!$this->authorizationChecker->isGranted(
            $request->attributes->get("_api_${operationType}_operation_name"),
            $isCollection
                ? $request->attributes->get('_api_resource_class')
                : $event->getControllerResult()
        )) {
            throw new AccessDeniedException(
                "You are not granted to access this {$operationType} resource."
            );
        }
    }
}

It works well for any basic API operation.

But with this custom operation:

 *         "validate" = {
 *             "method" = "PUT",
 *             "path" = "/ssl_certificates/{id}/validate",
 *             "controller" = SslCertificateValidate::class
 *         }
namespace App\Controller\Api;

use AppBundle\Entity\SslCertificate;
use AppBundle\Manager\SslCertificateManager;

final class SslCertificateValidate
{
    /**
     * @var SslCertificateManager
     */
    private $sslCertificateManager;

    public function __invoke(SslCertificate $sslCertificate)
    {
        $this->sslCertificateManager->feed($sslCertificate);

        return [
            'verified' => $this->sslCertificateManager->verify($sslCertificate),
        ];
    }
}

The subscriber is never called. I check on the profiler, the kernel.view event is not triggered at all.

Am I missing something or is it a bug?

question

All 8 comments

Apparently, it's simply because the event is trigger after the controller data return. But in that case I don't understand why it works for the CRUD method.

I re-read the documentation of api-platform, I don't see anything else I could use to retrieve and check access from item/collection operations.

So I just have to use the controller arguments event:

public static function getSubscribedEvents()
{
    return [
        KernelEvents::CONTROLLER_ARGUMENTS => 'checkAccess',
    ];
}

public function checkAccess(FilterControllerArgumentsEvent $event): void
{
    $request = $event->getRequest();

    if (!$request->attributes->has('_api_collection_operation_name')
        &&
        !$request->attributes->has('_api_item_operation_name')
    ) {
        return;
    }

    $isCollection = $request->attributes->has('_api_collection_operation_name');
    $operationType = $isCollection ? 'collection' : 'item';

    if (!$this->authorizationChecker->isGranted(
        $request->attributes->get("_api_${operationType}_operation_name"),
        $isCollection
            ? $request->attributes->get('_api_resource_class')
            : $event->getArguments()[0]
    )) {
        throw new AccessDeniedException(
            "You are not granted to access this {$operationType} resource."
        );
    }
}

Maybe not directly related to API platform, but maybe a cookbook recipe about it would be great! :+1:

I let you decide about this, feel free to close the issue if there is nothing more to do for you.

A doc entry would be awesome!

Hmm yes, I just spent the last few hours wondering why my KernelEvents::REQUEST --> EventPriorities::PRE_DESERIALIZE custom function in a subscriber was running for some endpoints (normal CRUD) but not others (when specifying a my own controller)!

Documenting this would be very handy.

If you use an API Platform custom controller, the events should be triggered.

@dunglas some events seem to be and others not. The KernelEvents::VIEW --> EventPriorities::POST_VALIDATE defined function fired on my route with a custom controller, just not the KernelEvents::REQUEST --> EventPriorities::PRE_DESERIALIZE.

The route loads the entity via a different identifier then the main entity id which is why I needed to implement a custom controller. Perhaps this is why the PRE_DESERIALIZE event wasn't triggered? I guess at that stage in the process, the system didn't know what entity the posted data will end up as?

Can you help us document your issue / resolution ?

The KernelEvents::VIEW --> EventPriorities::POST_VALIDATE defined function fired on my route with a custom controller, just not the KernelEvents::REQUEST --> EventPriorities::PRE_DESERIALIZE.

That's not possible. The kernel.request event is triggered before your controller is even called. Using an (API Platform) built-in controller vs your own custom controller does not affect that at all.

Could we move this support question to the #api-platform channel on Symfony Slack please?

Was this page helpful?
0 / 5 - 0 ratings