Skip to content

Commit 5b0b74e

Browse files
committed
[HttpKernel] Add #[IsSignatureValid] attribute
1 parent 5af84c0 commit 5b0b74e

File tree

14 files changed

+478
-0
lines changed

14 files changed

+478
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* Allow using their name without added suffix when using `#[Target]` for custom services
99
* Deprecate `Symfony\Bundle\FrameworkBundle\Console\Application::add()` in favor of `Symfony\Bundle\FrameworkBundle\Console\Application::addCommand()`
1010
* Add `assertEmailAddressNotContains()` to the `MailerAssertionsTrait`
11+
* Add autoconfiguration tag `kernel.uri_signer` to `Symfony\Component\HttpFoundation\UriSigner`
1112

1213
7.3
1314
---

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class UnusedTagsPass implements CompilerPassInterface
6161
'kernel.fragment_renderer',
6262
'kernel.locale_aware',
6363
'kernel.reset',
64+
'kernel.uri_signer',
6465
'ldap',
6566
'mailer.transport_factory',
6667
'messenger.bus',

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,15 @@
9898
use Symfony\Component\HttpClient\ThrottlingHttpClient;
9999
use Symfony\Component\HttpClient\UriTemplateHttpClient;
100100
use Symfony\Component\HttpFoundation\Request;
101+
use Symfony\Component\HttpFoundation\UriSigner;
101102
use Symfony\Component\HttpKernel\Attribute\AsController;
102103
use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver;
103104
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
104105
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
105106
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
106107
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
107108
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
109+
use Symfony\Component\HttpKernel\EventListener\IsSignatureValidAttributeListener;
108110
use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator;
109111
use Symfony\Component\HttpKernel\Profiler\ProfilerStateChecker;
110112
use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
@@ -275,6 +277,9 @@ public function load(array $configs, ContainerBuilder $container): void
275277
if (!class_exists(RunProcessMessageHandler::class)) {
276278
$container->removeDefinition('process.messenger.process_message_handler');
277279
}
280+
if (!class_exists(IsSignatureValidAttributeListener::class)) {
281+
$container->removeDefinition('controller.is_signature_valid_attribute_listener');
282+
}
278283

279284
if ($this->hasConsole()) {
280285
$loader->load('console.php');
@@ -749,6 +754,8 @@ public function load(array $configs, ContainerBuilder $container): void
749754
->addTag('mime.mime_type_guesser');
750755
$container->registerForAutoconfiguration(LoggerAwareInterface::class)
751756
->addMethodCall('setLogger', [new Reference('logger')]);
757+
$container->registerForAutoconfiguration(UriSigner::class)
758+
->addTag('kernel.uri_signer');
752759

753760
$container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) {
754761
$tagAttributes = get_object_vars($attribute);

src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Symfony\Component\HttpKernel\EventListener\CacheAttributeListener;
3333
use Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener;
3434
use Symfony\Component\HttpKernel\EventListener\ErrorListener;
35+
use Symfony\Component\HttpKernel\EventListener\IsSignatureValidAttributeListener;
3536
use Symfony\Component\HttpKernel\EventListener\LocaleListener;
3637
use Symfony\Component\HttpKernel\EventListener\ResponseListener;
3738
use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener;
@@ -148,6 +149,13 @@
148149
->tag('kernel.event_subscriber')
149150
->tag('kernel.reset', ['method' => '?reset'])
150151

152+
->set('controller.is_signature_valid_attribute_listener', IsSignatureValidAttributeListener::class)
153+
->args([
154+
service('uri_signer'),
155+
tagged_locator('kernel.uri_signer'),
156+
])
157+
->tag('kernel.event_subscriber')
158+
151159
->set('controller.helper', ControllerHelper::class)
152160
->tag('container.service_subscriber')
153161

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead
8+
* Add `#[WithHttpStatus]` to define status codes: 404 for `SignedUriException` and 403 for `ExpiredSignedUriException`
89

910
7.3
1011
---

src/Symfony/Component/HttpFoundation/Exception/ExpiredSignedUriException.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111

1212
namespace Symfony\Component\HttpFoundation\Exception;
1313

14+
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\Component\HttpKernel\Attribute\WithHttpStatus;
16+
1417
/**
1518
* @author Kevin Bond <kevinbond@gmail.com>
1619
*/
20+
#[WithHttpStatus(Response::HTTP_FORBIDDEN)]
1721
final class ExpiredSignedUriException extends SignedUriException
1822
{
1923
/**

src/Symfony/Component/HttpFoundation/Exception/SignedUriException.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111

1212
namespace Symfony\Component\HttpFoundation\Exception;
1313

14+
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\Component\HttpKernel\Attribute\WithHttpStatus;
16+
1417
/**
1518
* @author Kevin Bond <kevinbond@gmail.com>
1619
*/
20+
#[WithHttpStatus(Response::HTTP_NOT_FOUND)]
1721
abstract class SignedUriException extends \RuntimeException implements ExceptionInterface
1822
{
1923
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Attribute;
13+
14+
/**
15+
* Validates the request signature for specific HTTP methods.
16+
*
17+
* This class determines whether a request's signature should be validated
18+
* based on the configured HTTP methods. If the request method matches one
19+
* of the specified methods (or if no methods are specified), the signature
20+
* is checked.
21+
*
22+
* If the signature is invalid, a {@see \Symfony\Component\HttpFoundation\Exception\SignedUriException}
23+
* is thrown during validation.
24+
*
25+
* @author Santiago San Martin <sanmartindev@gmail.com>
26+
*/
27+
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)]
28+
final class IsSignatureValid
29+
{
30+
public readonly array $methods;
31+
public readonly ?string $signer;
32+
33+
/**
34+
* @param string[]|string $methods HTTP methods that require signature validation. An empty array means that no method filtering is done
35+
* @param string $signer The ID of the UriSigner service to use for signature validation. Defaults to 'uri_signer'
36+
*/
37+
public function __construct(
38+
array|string $methods = [],
39+
?string $signer = null,
40+
) {
41+
$this->methods = (array) $methods;
42+
$this->signer = $signer;
43+
}
44+
}

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.4
5+
---
6+
7+
* Add `#[IsSignatureValid]` attribute to validate URI signatures
8+
49
7.3
510
---
611

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\EventListener;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16+
use Symfony\Component\HttpFoundation\UriSigner;
17+
use Symfony\Component\HttpKernel\Attribute\IsSignatureValid;
18+
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
19+
use Symfony\Component\HttpKernel\KernelEvents;
20+
21+
/**
22+
* Handles the IsSignatureValid attribute.
23+
*
24+
* @author Santiago San Martin <sanmartindev@gmail.com>
25+
*/
26+
class IsSignatureValidAttributeListener implements EventSubscriberInterface
27+
{
28+
public function __construct(
29+
private readonly UriSigner $uriSigner,
30+
private readonly ContainerInterface $container,
31+
) {
32+
}
33+
34+
public function onKernelControllerArguments(ControllerArgumentsEvent $event): void
35+
{
36+
if (!$attributes = $event->getAttributes(IsSignatureValid::class)) {
37+
return;
38+
}
39+
40+
$request = $event->getRequest();
41+
foreach ($attributes as $attribute) {
42+
$methods = array_map('strtoupper', $attribute->methods);
43+
if ($methods && !\in_array($request->getMethod(), $methods, true)) {
44+
continue;
45+
}
46+
47+
if (null === $attribute->signer) {
48+
$this->uriSigner->verify($request);
49+
continue;
50+
}
51+
52+
$signer = $this->container->get($attribute->signer);
53+
if (!$signer instanceof UriSigner) {
54+
throw new \LogicException(\sprintf('The service "%s" is not an instance of "%s".', $attribute->signer, UriSigner::class));
55+
}
56+
57+
$signer->verify($request);
58+
}
59+
}
60+
61+
public static function getSubscribedEvents(): array
62+
{
63+
return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 30]];
64+
}
65+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy