attachinline-8.x-1.2/src/EventSubscriber/CspSubscriber.php
src/EventSubscriber/CspSubscriber.php
<?php
namespace Drupal\attachinline\EventSubscriber;
use Drupal\csp\Csp;
use Drupal\csp\CspEvents;
use Drupal\csp\Event\PolicyAlterEvent;
use Drupal\csp\Nonce;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Act on CSP events.
*/
class CspSubscriber implements EventSubscriberInterface {
/**
* An array of hashes keyed by directive name.
*
* @var array[]
*/
protected $directiveHashList = [];
/**
* The CSP Nonce service if the module is enable.
*
* @var ?\Drupal\csp\Nonce
*/
protected ?Nonce $nonce;
/**
* An array of directives to enable nonce for.
*
* @var string[]
*/
protected $directiveNonceList = [];
/**
* CspSubscriber constructor.
*
* @param ?\Drupal\csp\Nonce $nonce
* The CSP nonce service.
*/
public function __construct(
?Nonce $nonce = NULL,
) {
$this->nonce = $nonce;
}
/**
* {@inheritDoc}
*/
public static function getSubscribedEvents(): array {
if (!class_exists(CspEvents::class)) {
return [];
}
$events[CspEvents::POLICY_ALTER] = [
// Execute later in case other listeners add 'unsafe-inline'.
['onCspPolicyAlter', -10],
];
return $events;
}
/**
* Register a hash to be applied to the current request's CSP policies.
*
* @param string $directive
* The directive name.
* @param string $hash
* The hash value.
*/
public function registerHash(string $directive, string $hash) {
$this->directiveHashList[$directive][] = "'" . $hash . "'";
}
/**
* Retrieve the nonce value for the current request.
*
* @deprecated in attachinline:8.x-1.5 and is removed from attachinline:2.0.0.
* Use \Drupal\csp\Nonce::getValue() instead.
* @see https://www.drupal.org/project/attachinline/issues/3389256
*
* @return string
* The nonce value.
*/
public function getNonce() {
@trigger_error('CspSubscriber::getNonce() is deprecated in attachinline:8.x-1.5 and is removed from attachinline:2.0.0. Use \Drupal\csp\Nonce::getValue() instead. See https://www.drupal.org/project/attachinline/issues/3389256', E_USER_DEPRECATED);
return $this->nonce->getValue();
}
/**
* Enable nonce for a directive.
*
* @param string $directive
* The directive to enable nonce for.
*/
public function registerNonce(string $directive) {
$this->directiveNonceList[] = $directive;
}
/**
* Add hashes to the provided policy.
*
* Hashes are only added if the corresponding directive is enabled in module
* configuration.
*
* Only add hashes if the policy does not already include 'unsafe-inline',
* otherwise non-hashed inline JS may be unexpectedly blocked.
*
* @param \Drupal\csp\Event\PolicyAlterEvent $event
* The Policy Alter Event.
*/
public function onCspPolicyAlter(PolicyAlterEvent $event) {
$policy = $event->getPolicy();
foreach ($this->directiveHashList as $directive => $hashes) {
static::fallbackAwareAppendIfEnabled($policy, $directive, $hashes);
}
foreach ($this->directiveNonceList as $directive) {
static::fallbackAwareAppendIfEnabled($policy, $directive, [$this->nonce->getSource()]);
}
}
/**
* Append to a directive if it or a fallback directive is enabled.
*
* If the specified directive is not enabled but one of its fallback
* directives is, it will be initialized with the same value as the fallback
* before appending the new value.
*
* If none of the specified directive's fallbacks are enabled, the directive
* will not be enabled.
*
* @param \Drupal\csp\Csp $policy
* The policy to alter.
* @param string $directive
* The directive name.
* @param array $value
* The directive value.
*
* @see Csp::fallbackAwareAppendIfEnabled()
*/
protected static function fallbackAwareAppendIfEnabled(Csp $policy, string $directive, array $value) {
if ($policy->hasDirective($directive)) {
if (!in_array(Csp::POLICY_UNSAFE_INLINE, $policy->getDirective($directive))) {
$policy->appendDirective($directive, $value);
}
return;
}
// Duplicate the closest enabled fallback directive.
foreach ($policy::getDirectiveFallbackList($directive) as $fallback) {
if ($policy->hasDirective($fallback)) {
$fallbackValue = $policy->getDirective($fallback);
// Don't make any modifications if closest enabled fallback uses
// 'unsafe-inline'.
if (in_array(Csp::POLICY_UNSAFE_INLINE, $fallbackValue)) {
return;
}
$policy->setDirective($directive, $fallbackValue);
$policy->appendDirective($directive, $value);
return;
}
}
}
}
