Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to use custom request matcher #241

Merged
merged 2 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
### 3.x.x (xxxx-xx-xx)
* Added `csp > request_matcher` option to allow the use of a custom request matcher (`Symfony\Component\HttpFoundation\RequestMatcherInterface`)

### 3.1.0 (2023-12-03)
* Fixed overriding CSP header
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 @@ -57,6 +57,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 @@ -41,6 +42,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 @@ -53,7 +55,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 @@ -62,6 +65,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 @@ -149,7 +153,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