Skip to content

Commit

Permalink
Allow to use custom request matcher
Browse files Browse the repository at this point in the history
This adds an option to allow the use of a custom
request matcher, e.g. to exclude certain paths.
  • Loading branch information
ihmels committed Jul 11, 2023
1 parent 93f8009 commit 7769ba4
Show file tree
Hide file tree
Showing 5 changed files with 23 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### 3.x.x (xxxx-xx-xx)
* Fixed overriding CSP header
* Added `csp > request_matcher` option to allow the use of a custom request matcher (`Symfony\Component\HttpFoundation\RequestMatcherInterface`)

### 3.0.0 (2022-03-17)
* Bump minimal PHP version to 7.4
Expand Down
1 change: 1 addition & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ private function addCspNode(): ArrayNodeDefinition
->canBeDisabled()
// CSP is enabled by default to ensure BC
->children()
->scalarNode('request_matcher')->defaultNull()->end()
->arrayNode('hosts')->scalarPrototype()->end()->defaultValue([])->end()
->arrayNode('content_types')->scalarPrototype()->end()->defaultValue([])->end()
->arrayNode('report_endpoint')
Expand Down
4 changes: 4 additions & 0 deletions src/DependencyInjection/NelmioSecurityExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public function load(array $configs, ContainerBuilder $container): void
$cspListenerDefinition->setArguments([$reportDefinition, $enforceDefinition, new Reference('nelmio_security.nonce_generator'), new Reference('nelmio_security.sha_computer'), (bool) $cspConfig['compat_headers'], $cspConfig['hosts'], $cspConfig['content_types']]);
$container->setParameter('nelmio_security.csp.hash_algorithm', $cspConfig['hash']['algorithm']);

if (isset($cspConfig['request_matcher'])) {
$cspListenerDefinition->setArgument(7, new Reference($cspConfig['request_matcher']));
}

$cspViolationLogFilterDefinition = $container->getDefinition('nelmio_security.csp_report.filter');

$container->setParameter('nelmio_security.csp.report_log_level', $cspConfig['report_endpoint']['log_level']);
Expand Down
14 changes: 12 additions & 2 deletions src/EventListener/ContentSecurityPolicyListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Nelmio\SecurityBundle\ContentSecurityPolicy\NonceGeneratorInterface;
use Nelmio\SecurityBundle\ContentSecurityPolicy\ShaComputerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
Expand All @@ -43,6 +44,7 @@ final class ContentSecurityPolicyListener extends AbstractContentTypeRestrictabl
private ?array $sha = null;
private NonceGeneratorInterface $nonceGenerator;
private ShaComputerInterface $shaComputer;
private ?RequestMatcherInterface $requestMatcher;

/**
* @param list<string> $hosts
Expand All @@ -55,7 +57,8 @@ public function __construct(
ShaComputerInterface $shaComputer,
bool $compatHeaders = true,
array $hosts = [],
array $contentTypes = []
array $contentTypes = [],
?RequestMatcherInterface $requestMatcher = null
) {
parent::__construct($contentTypes);
$this->report = $report;
Expand All @@ -64,6 +67,7 @@ public function __construct(
$this->hosts = $hosts;
$this->nonceGenerator = $nonceGenerator;
$this->shaComputer = $shaComputer;
$this->requestMatcher = $requestMatcher;
}

public function onKernelRequest(RequestEvent $e): void
Expand Down Expand Up @@ -151,7 +155,13 @@ public function onKernelResponse(ResponseEvent $e): void
return;
}

if (([] === $this->hosts || \in_array($e->getRequest()->getHost(), $this->hosts, true)) && $this->isContentTypeValid($response)) {
if ($this->requestMatcher) {
$match = $this->requestMatcher->matches($request);
} else {
$match = ([] === $this->hosts || \in_array($e->getRequest()->getHost(), $this->hosts, true)) && $this->isContentTypeValid($response);
}

if ($match) {
$signatures = $this->sha;
if (null !== $this->scriptNonce) {
$signatures['script-src'][] = 'nonce-'.$this->scriptNonce;
Expand Down
5 changes: 5 additions & 0 deletions src/Resources/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,18 @@ Finally, an optional ``hosts`` key lets you configure which hostnames (e.g. ``fo
the CSP rule should be enforced on. If the list is empty (it is by default), all
hostnames will use the CSP rule.

If the `content_types` and `hosts` options don’t fit your needs, you can also configure a service implementing
`Symfony\Component\HttpFoundation\RequestMatcherInterface` as `request_matcher`. Then the `content_types` and `hosts`
options are no longer used.

.. code-block:: yaml
# config/packages/nelmio_security.yaml
nelmio_security:
csp:
enabled: true
report_logger_service: logger
request_matcher: null
hosts: []
content_types: []
enforce:
Expand Down

0 comments on commit 7769ba4

Please sign in to comment.