8000 feat(HttpKernel): add `#[IsSignatureValid]` attribute with exception-… · symfony/symfony@c000b69 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit c000b69

Browse files
committed
feat(HttpKernel): add #[IsSignatureValid] attribute with exception-based handling
1 parent e9f91a6 commit c000b69

File tree

9 files changed

+507
-2
lines changed

9 files changed

+507
-2
lines changed

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

Lines changed: 7 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,11 @@
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+
])
332+
->tag('kernel.event_subscriber')
326333
;
327334
};

src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"symfony/password-hasher": "^6.4|^7.0",
2929
"symfony/security-core": "^7.3",
3030
"symfony/security-csrf": "^6.4|^7.0",
31-
"symfony/security-http": "^7.3",
31+
"symfony/security-http": "^7.4",
3232
"symfony/service-contracts": "^2.5|^3"
3333
},
3434
"require-dev": {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\Response;
15+
16+
/**
17+
* Attribute to ensure the request URI contains a valid signature before allowing controller execution.
18+
*
19+
* When applied, this attribute verifies that the request is signed and the signature is still valid (e.g., not expired).
20+
* Behavior can be customized to either return a specific HTTP status code or throw an exception to be handled globally.
21+
*
22+
* @author Santiago San Martin <sanmartindev@gmail.com>
23+
*/
24+
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)]
25+
final class IsSignatureValid
26+
{
27+
/**
28+
* @param int|null $validationFailedStatusCode The HTTP status code to return if the signature is invalid.
29+
* @param bool|null $throw If true, an exception is thrown on signature failure instead of returning a response.
30+
* @param array|string $methods HTTP methods that require signature validation.
31+
*/
32+
public function __construct(
33+
public ?int $validationFailedStatusCode = Response::HTTP_NOT_FOUND,
34+
public ?bool $throw = null,
35+
public array|string $methods = [],
36+
) {
37+
}
38+
}

src/Symfony/Component/Security/Http/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: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\HttpFoundation\UriSigner;
16+
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
17+
use Symfony\Component\HttpKernel\Exception\HttpException;
18+
use Symfony\Component\HttpKernel\KernelEvents;
19+
use Symfony\Component\Security\Http\Attribute\IsSignatureValid;
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+
) {
31+
}
32+
33+
public function onKernelControllerArguments(ControllerArgumentsEvent $event): void
34+
{
35+
/** @var IsSignatureValid[] $attributes */
36+
if (!\is_array($attributes = $event->getAttributes()[IsSignatureValid::class] ?? null)) {
37+
return;
38+
}
39+
40+
$request = $event->getRequest();
41+
foreach ($attributes as $attribute) {
42+
$methods = \array_map('strtoupper', (array) $attribute->methods);
43+
if ($methods && !\in_array($request->getMethod(), $methods, true)) {
44+
continue;
45+
}
46+
47+
if ($attribute->throw) {
48+
$this->uriSigner->verify($request);
49+
continue;
50+
}
51+
if (!$this->uriSigner->checkRequest($request)) {
52+
throw new HttpException($attribute->validationFailedStatusCode, 'The URI signature is invalid.');
53+
}
54+
}
55+
}
56+
57+
public static function getSubscribedEvents(): array
58+
{
59+
return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 30]];
60+
}
61+
}

0 commit comments

Comments
 (0)
0