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?
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?