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 = 1 === \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, [80, 443]) ? '' : ":{$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($content, 0, $headTagPosition) .
  165. $compressedSnippet .
  166. substr($content, $headTagPosition);
  167. $response->setContent($content);
  168. }
  169. }
  170. class_alias(InjectCrossOriginHelperSubscriber::class, 'EzSystems\EzPlatformPageBuilder\Event\Subscriber\InjectCrossOriginHelperSubscriber');