<?php
namespace App\Security;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security;
/**
* Cloisonnement multi-entreprise (SaaS) : toute entité métier injectée dans un
* contrôleur (ParamConverter "{id}") doit appartenir à l'entreprise connectée.
*
* Point unique de contrôle : les contrôleurs n'ont pas à vérifier eux-mêmes le
* propriétaire des entités reçues en argument. Une entité d'une autre
* entreprise répond 404 (on ne révèle pas son existence), un visiteur non
* connecté est renvoyé vers la connexion. Les règles sont dans TenantGuard.
*/
class TenantOwnershipSubscriber implements EventSubscriberInterface
{
private $security;
private $tenantGuard;
public function __construct(Security $security, TenantGuard $tenantGuard)
{
$this->security = $security;
$this->tenantGuard = $tenantGuard;
}
public static function getSubscribedEvents(): array
{
return [KernelEvents::CONTROLLER_ARGUMENTS => 'onControllerArguments'];
}
public function onControllerArguments(ControllerArgumentsEvent $event): void
{
foreach ($event->getArguments() as $argument) {
if (!is_object($argument) || !$this->tenantGuard->isTenantEntity($argument)) {
continue;
}
if (!$this->security->getUser()) {
throw new AccessDeniedException('Connexion requise.');
}
$this->tenantGuard->denyUnlessCanAccess($argument);
}
}
}