lupus_decoupled-1.x-dev/modules/lupus_decoupled_responsive_preview/src/EventSubscriber/ContentSecurityEventSubscriber.php
modules/lupus_decoupled_responsive_preview/src/EventSubscriber/ContentSecurityEventSubscriber.php
<?php
namespace Drupal\lupus_decoupled_responsive_preview\EventSubscriber;
use Drupal\lupus_decoupled_ce_api\BaseUrlProviderTrait;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Response subscriber to alter content security for the preview in an iframe.
*
* This is required to allow the responsive-preview iframe to display frontend
* pages.
*/
class ContentSecurityEventSubscriber implements EventSubscriberInterface {
use BaseUrlProviderTrait;
/**
* Sets extra headers on successful responses.
*
* @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
* The event to process.
*
* @throws \Exception
*/
public function onRespond(ResponseEvent $event) {
if (!$event->isMainRequest()) {
return;
}
// Get all URLs for the front end; replace hostname parts that contain
// underscores with '*'.
$all_base_urls = $this->getBaseUrlProvider()->getAllFrontendBaseUrls() ?? [];
foreach ($all_base_urls as &$url) {
if (is_string($url) && str_contains($url, '_')) {
$parsed_url = parse_url($url);
$hostname_parts = explode('.', $parsed_url['host'] ?? '');
foreach ($hostname_parts as &$part) {
if (str_contains($part, '_')) {
$part = '*';
}
}
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
$url = $scheme . implode('.', $hostname_parts)
. (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
}
}
unset($url);
unset($hostname_parts);
$policy_urls = array_unique($all_base_urls);
// Set the csp header.
if (!empty($policy_urls)) {
// Add self to urls.
$policy_urls[] = "'self'";
$this->setFrameSrcDirective($event->getResponse(), $policy_urls);
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
public static function getSubscribedEvents() {
// Make sure this runs after SeckitEventSubscriber.
$events[KernelEvents::RESPONSE][] = ['onRespond', -100];
return $events;
}
/**
* Adds frame-src directive to CSP header.
*
* @param \Symfony\Component\HttpFoundation\Response $response
* The response.
* @param array $policy_urls
* Array of urls.
*/
protected function setFrameSrcDirective(Response $response, array $policy_urls) {
$csp_header_values = [];
// If seckit module already set the header, we need to add the directive.
if ($response->headers->has('Content-Security-Policy')) {
$value = $response->headers->get('Content-Security-Policy');
// Remove frame-src directives coming from seckit.
$regex = "/(frame-src)[^;]+(;|$)/";
$value = preg_replace($regex, "", $value);
$csp_header_values[] = preg_replace($regex, "", $value);
}
$csp_header_values[] = 'frame-src ' . implode(' ', $policy_urls);
$response->headers->set('Content-Security-Policy', implode('; ', $csp_header_values), TRUE);
}
}
