You are here

ResponseCspSubscriber.php in Content-Security-Policy 8

File

src/EventSubscriber/ResponseCspSubscriber.php
View source
<?php

namespace Drupal\csp\EventSubscriber;

use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\csp\Csp;
use Drupal\csp\CspEvents;
use Drupal\csp\Event\PolicyAlterEvent;
use Drupal\csp\LibraryPolicyBuilder;
use Drupal\csp\ReportingHandlerPluginManager;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Class ResponseSubscriber.
 */
class ResponseCspSubscriber implements EventSubscriberInterface {

  /**
   * The Config Factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The Library Policy Builder service.
   *
   * @var \Drupal\csp\LibraryPolicyBuilder
   */
  protected $libraryPolicyBuilder;

  /**
   * The Reporting Handler Plugin Manager service.
   *
   * @var \Drupal\csp\ReportingHandlerPluginManager
   */
  private $reportingHandlerPluginManager;

  /**
   * The Event Dispatcher service.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  private $eventDispatcher;

  /**
   * Constructs a new ResponseSubscriber object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Config Factory service.
   * @param \Drupal\csp\LibraryPolicyBuilder $libraryPolicyBuilder
   *   The Library Parser service.
   * @param \Drupal\csp\ReportingHandlerPluginManager $reportingHandlerPluginManager
   *   The Reporting Handler Plugin Manager service.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
   *   The Event Dispatcher Service.
   */
  public function __construct(ConfigFactoryInterface $configFactory, LibraryPolicyBuilder $libraryPolicyBuilder, ReportingHandlerPluginManager $reportingHandlerPluginManager, EventDispatcherInterface $eventDispatcher) {
    $this->configFactory = $configFactory;
    $this->libraryPolicyBuilder = $libraryPolicyBuilder;
    $this->reportingHandlerPluginManager = $reportingHandlerPluginManager;
    $this->eventDispatcher = $eventDispatcher;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[KernelEvents::RESPONSE] = [
      'onKernelResponse',
    ];
    return $events;
  }

  /**
   * Add Content-Security-Policy header to response.
   *
   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
   *   The Response event.
   */
  public function onKernelResponse(FilterResponseEvent $event) {
    if (!$event
      ->isMasterRequest()) {
      return;
    }
    $cspConfig = $this->configFactory
      ->get('csp.settings');
    $libraryDirectives = $this->libraryPolicyBuilder
      ->getSources();
    $response = $event
      ->getResponse();
    if ($response instanceof CacheableResponseInterface) {
      $response
        ->getCacheableMetadata()
        ->addCacheTags([
        'config:csp.settings',
      ]);
    }
    foreach ([
      'report-only',
      'enforce',
    ] as $policyType) {
      if (!$cspConfig
        ->get($policyType . '.enable')) {
        continue;
      }
      $policy = new Csp();
      $policy
        ->reportOnly($policyType == 'report-only');
      foreach ($cspConfig
        ->get($policyType . '.directives') ?: [] as $directiveName => $directiveOptions) {
        if (is_bool($directiveOptions)) {
          $policy
            ->setDirective($directiveName, TRUE);
          continue;
        }

        // This is a directive with a simple array of values.
        if (!isset($directiveOptions['base'])) {
          $policy
            ->setDirective($directiveName, $directiveOptions);
          continue;
        }
        switch ($directiveOptions['base']) {
          case 'self':
            $policy
              ->setDirective($directiveName, [
              Csp::POLICY_SELF,
            ]);
            break;
          case 'none':
            $policy
              ->setDirective($directiveName, [
              Csp::POLICY_NONE,
            ]);
            break;
          case 'any':
            $policy
              ->setDirective($directiveName, [
              Csp::POLICY_ANY,
            ]);
            break;
          default:

            // Initialize to an empty value so that any alter subscribers can
            // tell that this directive was enabled.
            $policy
              ->setDirective($directiveName, []);
        }
        if (!empty($directiveOptions['flags'])) {
          $policy
            ->appendDirective($directiveName, array_map(function ($value) {
            return "'" . $value . "'";
          }, $directiveOptions['flags']));
        }
        if (!empty($directiveOptions['sources'])) {
          $policy
            ->appendDirective($directiveName, $directiveOptions['sources']);
        }
        if (isset($libraryDirectives[$directiveName])) {
          $policy
            ->appendDirective($directiveName, $libraryDirectives[$directiveName]);
        }
      }
      $reportingPluginId = $cspConfig
        ->get($policyType . '.reporting.plugin');
      if ($reportingPluginId) {
        $reportingOptions = $cspConfig
          ->get($policyType . '.reporting.options') ?: [];
        $reportingOptions += [
          'type' => $policyType,
        ];
        try {
          $this->reportingHandlerPluginManager
            ->createInstance($reportingPluginId, $reportingOptions)
            ->alterPolicy($policy);
        } catch (PluginException $e) {
          watchdog_exception('csp', $e);
        }
      }
      $this->eventDispatcher
        ->dispatch(CspEvents::POLICY_ALTER, new PolicyAlterEvent($policy, $response));
      if ($headerValue = $policy
        ->getHeaderValue()) {
        $response->headers
          ->set($policy
          ->getHeaderName(), $headerValue);
      }
    }
  }

}

Classes

Namesort descending Description
ResponseCspSubscriber Class ResponseSubscriber.