Skip to content

Commit a7d8c95

Browse files
committed
[HttpFoundation] Add #[IsSignatureValid] attribute
1 parent 36d8d5d commit a7d8c95

File tree

15 files changed

+482
-0
lines changed

15 files changed

+482
-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 `security.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
@@ -89,6 +89,7 @@ class UnusedTagsPass implements CompilerPassInterface
8989
'security.authenticator.login_linker',
9090
'security.expression_language_provider',
9191
'security.remember_me_handler',
92+
'security.uri_signer',
9293
'security.voter',
9394
'serializer.encoder',
9495
'serializer.normalizer',

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
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;
@@ -749,6 +750,8 @@ public function load(array $configs, ContainerBuilder $container): void
749750
->addTag('mime.mime_type_guesser');
750751
$container->registerForAutoconfiguration(LoggerAwareInterface::class)
751752
->addMethodCall('setLogger', [new Reference('logger')]);
753+
$container->registerForAutoconfiguration(UriSigner::class)
754+
->addTag('security.uri_signer');
752755

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

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator;
6565
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener;
6666
use Symfony\Component\Security\Http\Event\CheckPassportEvent;
67+
use Symfony\Component\Security\Http\EventListener\IsSignatureValidAttributeListener;
6768

6869
/**
6970
* SecurityExtension.
@@ -134,6 +135,9 @@ public function load(array $configs, ContainerBuilder $container): void
134135
$container->removeDefinition('form.type_extension.form.password_hasher');
135136
$container->removeDefinition('form.type_extension.password.password_hasher');
136137
}
138+
if (!class_exists(IsSignatureValidAttributeListener::class)) {
139+
$container->removeDefinition('controller.is_signature_valid_attribute_listener');
140+
}
137141

138142
// set some global scalars
139143
$container->setParameter('security.access.denied_url', $config['access_denied_url']);

src/Symfony/Bundle/SecurityBundle/Resources/config/security.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
use Symfony\Component\Security\Http\Controller\SecurityTokenValueResolver;
4949
use Symfony\Component\Security\Http\Controller\UserValueResolver;
5050
use Symfony\Component\Security\Http\EventListener\IsGrantedAttributeListener;
51+
use Symfony\Component\Security\Http\EventListener\IsSignatureValidAttributeListener;
5152
use Symfony\Component\Security\Http\Firewall;
5253
use Symfony\Component\Security\Http\FirewallMapInterface;
5354
use Symfony\Component\Security\Http\HttpUtils;
@@ -323,5 +324,12 @@
323324
->set('cache.security_is_csrf_token_valid_attribute_expression_language')
324325
->parent('cache.system')
325326
->tag('cache.pool')
327+
328+
->set('controller.is_signature_valid_attribute_listener', IsSignatureValidAttributeListener::class)
329+
->args([
330+
service('uri_signer'),
331+
tagged_locator('security.uri_signer'),
332+
])
333+
->tag('kernel.event_subscriber')
326334
;
327335
};

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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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\Security\Http\Attribute;
13+
14+
use Symfony\Component\HttpFoundation\UriSigner;
15+
16+
/**
17+
* Validates the request signature for specific HTTP methods.
18+
*
19+
* This class determines whether a request's signature should be validated
20+
* based on the configured HTTP methods. If the request method matches one
21+
* of the specified methods (or if no methods are specified), the signature
22+
* is checked.
23+
*
24+
* If the signature is invalid, a {@see \Symfony\Component\HttpFoundation\Exception\SignedUriException}
25+
* is thrown during validation.
26+
*
27+
* @author Santiago San Martin <sanmartindev@gmail.com>
28+
*/
29+
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)]
30+
final class IsSignatureValid
31+
{
32+
public readonly array $methods;
33+
public readonly ?string $signer;
34+
35+
/**
36+
* @param string[]|string $methods HTTP methods that require signature validation. An empty array means that no method filtering is done
37+
* @param string $signer The ID of the UriSigner service to use for signature validation. Defaults to 'uri_signer'
38+
*
39+
* @throws \LogicException If the UriSigner class does not support the 'verify' method (requires symfony/http-foundation >= 7.3)
40+
*/
41+
public function __construct(
42+
array|string $methods = [],
43+
?string $signer = null,
44+
) {
45+
if (!method_exists(UriSigner::class, 'verify')) {
46+
throw new \LogicException('The `IsSignatureValid` attribute requires symfony/http-foundation >= 7.3.');
47+
}
48+
49+
$this->methods = (array) $methods;
50+
$this->signer = $signer;
51+
}
52+
}

src/Symfony/Component/Security/Http/CHANGELOG.md

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

77
* Deprecate callable firewall listeners, extend `AbstractListener` or implement `FirewallListenerInterface` instead
88
* Deprecate `AbstractListener::__invoke`
9+
* Add `#[IsSignatureValid]` attribute to validate URI signatures
910

1011
7.3
1112
---

0 commit comments

Comments
 (0)