vendor/ibexa/page-builder/src/lib/Event/Subscriber/InjectCrossOriginHelperSubscriber.php line 77

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4.  * @license For full copyright and license information view LICENSE file distributed with this source code.
  5.  */
  6. declare(strict_types=1);
  7. namespace Ibexa\PageBuilder\Event\Subscriber;
  8. use Ibexa\AdminUi\Specification\SiteAccess\IsAdmin;
  9. use Ibexa\Contracts\PageBuilder\PageBuilder\ConfigurationResolverInterface;
  10. use Ibexa\Core\MVC\Symfony\SiteAccess;
  11. use Ibexa\PageBuilder\Security\EditorialMode\PostAuthenticationGuardToken;
  12. use Ibexa\PageBuilder\Siteaccess\ReverseMatcher;
  13. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\HttpFoundation\Response;
  16. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  17. use Symfony\Component\HttpKernel\KernelEvents;
  18. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  19. use Twig\Environment;
  20. class InjectCrossOriginHelperSubscriber implements EventSubscriberInterface
  21. {
  22.     /** @var \Ibexa\Contracts\PageBuilder\PageBuilder\ConfigurationResolverInterface */
  23.     private $pageBuilderConfigResolver;
  24.     /** @var \Twig\Environment */
  25.     private $templating;
  26.     /** @var array */
  27.     private $siteaccessGroups;
  28.     /** @var \Ibexa\PageBuilder\Siteaccess\ReverseMatcher */
  29.     private $reverseMatcher;
  30.     /** @var \Ibexa\Core\MVC\Symfony\SiteAccess */
  31.     private $siteaccess;
  32.     /** @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface */
  33.     private $tokenStorage;
  34.     public function __construct(
  35.         ConfigurationResolverInterface $pageBuilderConfigResolver,
  36.         Environment $templating,
  37.         array $siteaccessGroups,
  38.         ReverseMatcher $reverseMatcher,
  39.         SiteAccess $siteaccess,
  40.         TokenStorageInterface $tokenStorage
  41.     ) {
  42.         $this->pageBuilderConfigResolver $pageBuilderConfigResolver;
  43.         $this->templating $templating;
  44.         $this->siteaccessGroups $siteaccessGroups;
  45.         $this->reverseMatcher $reverseMatcher;
  46.         $this->siteaccess $siteaccess;
  47.         $this->tokenStorage $tokenStorage;
  48.     }
  49.     /**
  50.      * Returns an array of event names this subscriber wants to listen to.
  51.      *
  52.      * @return array The event names to listen to
  53.      */
  54.     public static function getSubscribedEvents(): array
  55.     {
  56.         return [
  57.             KernelEvents::RESPONSE => 'onResponse',
  58.         ];
  59.     }
  60.     /**
  61.      * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
  62.      *
  63.      * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
  64.      */
  65.     public function onResponse(ResponseEvent $event)
  66.     {
  67.         if (!$event->isMainRequest()) {
  68.             return;
  69.         }
  70.         $request $event->getRequest();
  71.         $response $event->getResponse();
  72.         $isHelperEnabled $this->pageBuilderConfigResolver->get('inject_cross_origin_helper'$this->siteaccess->name);
  73.         if (null === $this->siteaccess || !$isHelperEnabled) {
  74.             return;
  75.         }
  76.         $isHtmlRequest 'html' === $request->getRequestFormat();
  77.         $isAttachment false !== stripos($response->headers->get('Content-Disposition'''), 'attachment');
  78.         $isHtmlResponse false !== strpos($response->headers->get('Content-Type'''), 'html');
  79.         if (
  80.             !$isHtmlRequest
  81.             || !$isHtmlResponse
  82.             || $isAttachment
  83.             || $response->isRedirection()
  84.             || $request->isXmlHttpRequest()
  85.             || !$response->getContent()
  86.             || !$this->isPreAuthenticatedFromPageBuilder()
  87.         ) {
  88.             return;
  89.         }
  90.         $isAdminSiteaccess = (new IsAdmin($this->siteaccessGroups))->isSatisfiedBy($this->siteaccess);
  91.         if ($isAdminSiteaccess) {
  92.             return;
  93.         }
  94.         if (true === $this->injectSnippetForSiteaccessHosts($request$response)) {
  95.             return;
  96.         }
  97.         $hosts $this->getCompatibleHosts($this->siteaccess->name);
  98.         $host reset($hosts);
  99.         $isAdminSiteaccessOnSameHost === \count($hosts) && $host === $request->getSchemeAndHttpHost();
  100.         if ($isAdminSiteaccessOnSameHost) {
  101.             return;
  102.         }
  103.         $this->injectSnippet($response$hosts);
  104.     }
  105.     private function isPreAuthenticatedFromPageBuilder(): bool
  106.     {
  107.         $token $this->tokenStorage->getToken();
  108.         return $token instanceof PostAuthenticationGuardToken;
  109.     }
  110.     /**
  111.      * @param string $requestSiteaccessName
  112.      *
  113.      * @return string[]
  114.      *
  115.      * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
  116.      */
  117.     private function getCompatibleHosts(string $requestSiteaccessName): array
  118.     {
  119.         $pageBuilderCompatibleAdminSiteaccesses $this->pageBuilderConfigResolver->reverseAdminSiteaccessMatch(
  120.             $requestSiteaccessName
  121.         );
  122.         $hosts = [];
  123.         foreach ($pageBuilderCompatibleAdminSiteaccesses as $siteaccessName) {
  124.             $hosts[] = $this->reverseMatcher->getSchemeAndHttpHost($siteaccessName);
  125.         }
  126.         return array_unique($hosts);
  127.     }
  128.     private function injectSnippetForSiteaccessHosts(
  129.         Request $request,
  130.         Response $response
  131.     ): bool {
  132.         $siteAccessHosts $this->pageBuilderConfigResolver->getSiteaccessHosts(
  133.             $this->siteaccess->name
  134.         );
  135.         if (empty($siteAccessHosts)) {
  136.             return false;
  137.         }
  138.         $scheme $request->getScheme();
  139.         $port $request->getPort();
  140.         $portPart in_array($port, [80443]) ? '' ":{$port}";
  141.         $siteAccessUrls = [];
  142.         foreach ($siteAccessHosts as $host) {
  143.             $siteAccessUrls[] = $scheme '://' $host $portPart;
  144.         }
  145.         $this->injectSnippet($response$siteAccessUrls);
  146.         return true;
  147.     }
  148.     /**
  149.      * @param \Symfony\Component\HttpFoundation\Response $response
  150.      * @param string[] $hosts
  151.      */
  152.     private function injectSnippet(Response $response, array $hosts): void
  153.     {
  154.         $content $response->getContent();
  155.         $headTagPosition stripos($content'</head>');
  156.         if (!$headTagPosition) {
  157.             return;
  158.         }
  159.         $snippet $this->templating->render(
  160.             '@IbexaPageBuilder/cross_origin_helper/snippet.html.twig',
  161.             ['hosts' => $hosts]
  162.         );
  163.         $compressedSnippet "\n" str_replace("\n"''$snippet) . "\n";
  164.         $content substr($content0$headTagPosition) .
  165.             $compressedSnippet .
  166.             substr($content$headTagPosition);
  167.         $response->setContent($content);
  168.     }
  169. }
  170. class_alias(InjectCrossOriginHelperSubscriber::class, 'EzSystems\EzPlatformPageBuilder\Event\Subscriber\InjectCrossOriginHelperSubscriber');