src/EventSubscriber/LoginTargetPathSubscriber.php line 47

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscriber;
  3. use App\Entity\BlogPost;
  4. use App\Entity\User;
  5. use App\Repository\BlogPostCategoryRepository;
  6. use App\Repository\BlogPostRepository;
  7. use App\Security\AppAuthenticator;
  8. use App\Service\SiteConfig;
  9. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. use Symfony\Component\HttpFoundation\RedirectResponse;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpKernel\Event\RequestEvent;
  14. use Symfony\Component\HttpKernel\KernelEvents;
  15. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  16. use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
  17. use Symfony\Component\Security\Core\Security;
  18. use Symfony\Component\Security\Http\Util\TargetPathTrait;
  19. class LoginTargetPathSubscriber implements EventSubscriberInterface
  20. {
  21.     use TargetPathTrait;
  22.     private const FIREWALL_MAIN 'main';
  23.     private const RUBRIQUE_ROUTES = ['podcast''categorie'];
  24.     public function __construct(
  25.         private Security $security,
  26.         private SiteConfig $siteConfig,
  27.         private UrlGeneratorInterface $urlGenerator,
  28.         private UrlMatcherInterface $urlMatcher,
  29.         private BlogPostRepository $blogPostRepository,
  30.         private BlogPostCategoryRepository $blogPostCategoryRepository,
  31.         private AuthorizationCheckerInterface $authorizationChecker,
  32.     ) {
  33.     }
  34.     public static function getSubscribedEvents(): array
  35.     {
  36.         return [
  37.             KernelEvents::REQUEST => 'onKernelRequest',
  38.         ];
  39.     }
  40.     public function onKernelRequest(RequestEvent $event): void
  41.     {
  42.         if (!$event->isMainRequest()) {
  43.             return;
  44.         }
  45.         $request $event->getRequest();
  46.         $routeName $request->attributes->get('_route');
  47.         if (
  48.             in_array($routeNameself::RUBRIQUE_ROUTEStrue)
  49.             && $request->getMethod() === 'GET'
  50.             && null === $this->security->getUser()
  51.             && !$this->siteConfig->isPublicFrontOffice()
  52.             && $request->hasSession()
  53.         ) {
  54.             $targetPath $request->getRequestUri();
  55.             $prepareUrl $this->urlGenerator->generate('app_prepare_sso', [
  56.                 'redirect' => $targetPath,
  57.             ]);
  58.             $event->setResponse(new RedirectResponse($prepareUrl));
  59.             return;
  60.         }
  61.         // Cas : utilisateur connecté qui accède à une rubrique — changer d'instance si nécessaire.
  62.         if (
  63.             in_array($routeNameself::RUBRIQUE_ROUTEStrue)
  64.             && $request->getMethod() === 'GET'
  65.             && ($connectedUser $this->security->getUser()) instanceof User
  66.             && $request->hasSession()
  67.         ) {
  68.             $this->switchInstanceIfNeeded($request$connectedUser);
  69.             // Ne pas rediriger : le contrôleur prend la main normalement.
  70.         }
  71.         if ($routeName !== AppAuthenticator::LOGIN_ROUTE) {
  72.             return;
  73.         }
  74.         if ($request->getMethod() !== 'GET') {
  75.             return;
  76.         }
  77.         if (null !== $this->security->getUser()) {
  78.             return;
  79.         }
  80.         if (!$request->hasSession()) {
  81.             return;
  82.         }
  83.         $session $request->getSession();
  84.         if ($this->getTargetPath($sessionself::FIREWALL_MAIN)) {
  85.             return;
  86.         }
  87.         $referer $request->headers->get('referer');
  88.         if (!$referer) {
  89.             return;
  90.         }
  91.         $targetPath $this->extractInternalPathFromReferer($referer$request->getHost());
  92.         if (null === $targetPath) {
  93.             return;
  94.         }
  95.         if (!$this->isRubriquePath($targetPath)) {
  96.             return;
  97.         }
  98.         $loginPath $this->urlGenerator->generate(AppAuthenticator::LOGIN_ROUTE);
  99.         $logoutPath $this->urlGenerator->generate('app_logout');
  100.         if (str_starts_with($targetPath$loginPath) || str_starts_with($targetPath$logoutPath)) {
  101.             return;
  102.         }
  103.         $this->saveTargetPath($sessionself::FIREWALL_MAIN$targetPath);
  104.     }
  105.     private function extractInternalPathFromReferer(string $refererstring $currentHost): ?string
  106.     {
  107.         $parts parse_url($referer);
  108.         if (false === $parts) {
  109.             return null;
  110.         }
  111.         $host $parts['host'] ?? null;
  112.         if ($host !== null && strcasecmp($host$currentHost) !== 0) {
  113.             return null;
  114.         }
  115.         $path $parts['path'] ?? null;
  116.         if (null === $path || $path === '') {
  117.             return null;
  118.         }
  119.         $query = isset($parts['query']) ? '?' $parts['query'] : '';
  120.         return $path $query;
  121.     }
  122.     /**
  123.      * Si l'instance courante en session ne donne pas accès au contenu cible,
  124.      * bascule automatiquement vers la première instance accessible de l'utilisateur.
  125.      */
  126.     private function switchInstanceIfNeeded(Request $requestUser $user): void
  127.     {
  128.         $path $request->getPathInfo();
  129.         try {
  130.             $matchedRoute $this->urlMatcher->match($path);
  131.         } catch (\Throwable) {
  132.             return;
  133.         }
  134.         $routeName $matchedRoute['_route'] ?? null;
  135.         $slug $matchedRoute['slug'] ?? null;
  136.         if (!$routeName || !$slug) {
  137.             return;
  138.         }
  139.         $session $request->getSession();
  140.         $currentInstanceId $session->get('selected_instance_id');
  141.         $accessibleInstances $this->resolveAccessibleInstances($routeName$slug$user);
  142.         if (empty($accessibleInstances)) {
  143.             return;
  144.         }
  145.         // Si l'instance courante est déjà valide, pas de changement.
  146.         foreach ($accessibleInstances as $instance) {
  147.             if ($instance->getId() === $currentInstanceId) {
  148.                 return;
  149.             }
  150.         }
  151.         // Basculer vers la première instance accessible.
  152.         $session->set('selected_instance_id'$accessibleInstances[0]->getId());
  153.     }
  154.     /**
  155.      * Retourne les instances activées auxquelles l'utilisateur a accès pour ce contenu.
  156.      *
  157.      * @return array<int, \App\Entity\Instance>
  158.      */
  159.     private function resolveAccessibleInstances(string $routeNamestring $slugUser $user): array
  160.     {
  161.         $instances = [];
  162.         if ($routeName === 'categorie') {
  163.             $category $this->blogPostCategoryRepository->findOneBy(['slug' => $slug]);
  164.             if (null === $category) {
  165.                 return [];
  166.             }
  167.             // Vérifie les critères métier via le voter VIEW_CONTENT.
  168.             if (!$this->authorizationChecker->isGranted('VIEW_CONTENT'$category)) {
  169.                 return [];
  170.             }
  171.             foreach ($category->getInstances() as $instance) {
  172.                 if ($instance->isActivated() && $user->getInstances()->contains($instance)) {
  173.                     $instances[] = $instance;
  174.                 }
  175.             }
  176.             return $instances;
  177.         }
  178.         // podcast
  179.         /** @var BlogPost|null $podcast */
  180.         $podcast $this->blogPostRepository->findOneBy(['slug' => $slug]);
  181.         if (null === $podcast) {
  182.             return [];
  183.         }
  184.         // Vérifie déjà les critères métier via le voter VIEW_CONTENT.
  185.         if (!$this->authorizationChecker->isGranted('VIEW_CONTENT'$podcast)) {
  186.             return [];
  187.         }
  188.         foreach ($podcast->getInstances() as $instance) {
  189.             if ($instance->isActivated() && $user->getInstances()->contains($instance)) {
  190.                 $instances[] = $instance;
  191.             }
  192.         }
  193.         return $instances;
  194.     }
  195.     private function isRubriquePath(string $targetPath): bool
  196.     {
  197.         $parsed parse_url($targetPath);
  198.         if (false === $parsed) {
  199.             return false;
  200.         }
  201.         $path $parsed['path'] ?? null;
  202.         if (!$path) {
  203.             return false;
  204.         }
  205.         try {
  206.             $matchedRoute $this->urlMatcher->match($path);
  207.         } catch (\Throwable) {
  208.             return false;
  209.         }
  210.         return in_array(($matchedRoute['_route'] ?? null), self::RUBRIQUE_ROUTEStrue);
  211.     }
  212. }