Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fec60e3

Browse files
committed
Add IsCsrfTokenValid attribute
1 parent 088bd94 commit fec60e3

File tree

7 files changed

+303
-0
lines changed

7 files changed

+303
-0
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface;
1818
use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener;
1919
use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener;
20+
use Symfony\Component\Security\Http\EventListener\IsCsrfTokenValidAttributeListener;
2021

2122
/**
2223
* @author Christian Flothmann <[email protected]>
@@ -41,6 +42,10 @@ private function registerCsrfProtectionListener(ContainerBuilder $container): vo
4142
$container->register('security.listener.csrf_protection', CsrfProtectionListener::class)
4243
->addArgument(new Reference('security.csrf.token_manager'))
4344
->addTag('kernel.event_subscriber');
45+
46+
$container->register('controller.is_csrf_token_valid_attribute_listener', IsCsrfTokenValidAttributeListener::class)
47+
->addArgument(new Reference('security.csrf.token_manager'))
48+
->addTag('kernel.event_subscriber');
4449
}
4550

4651
protected function registerLogoutHandler(ContainerBuilder $container): void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)]
15+
final class IsCsrfTokenValid
16+
{
17+
public function __construct(
18+
/**
19+
* Sets the id used when generating the token.
20+
*/
21+
public string $id,
22+
23+
/**
24+
* Sets the key of the request that contains the actual token value that should be validated.
25+
*/
26+
public ?string $tokenKey = '_token',
27+
) {
28+
}
29+
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.1
5+
---
6+
7+
* Add `#[IsCsrfTokenValid]` attribute
8+
49
7.0
510
---
611

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 <[email protected]>
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\HttpKernel\Event\ControllerArgumentsEvent;
16+
use Symfony\Component\HttpKernel\KernelEvents;
17+
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
18+
use Symfony\Component\Security\Csrf\CsrfToken;
19+
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
20+
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
21+
22+
/**
23+
* Handles the IsCsrfTokenValid attribute on controllers.
24+
*/
25+
class IsCsrfTokenValidAttributeListener implements EventSubscriberInterface
26+
{
27+
public function __construct(
28+
private readonly CsrfTokenManagerInterface $csrfTokenManager,
29+
) {
30+
}
31+
32+
public function onKernelControllerArguments(ControllerArgumentsEvent $event): void
33+
{
34+
/** @var IsCsrfTokenValid[] $attributes */
35+
if (!\is_array($attributes = $event->getAttributes()[IsCsrfTokenValid::class] ?? null)) {
36+
return;
37+
}
38+
39+
$request = $event->getRequest();
40+
41+
foreach ($attributes as $attribute) {
42+
if (!$this->csrfTokenManager->isTokenValid(new CsrfToken($attribute->id, $request->request->getString($attribute->tokenKey)))) {
43+
throw new InvalidCsrfTokenException('Invalid CSRF token.');
44+
}
45+
}
46+
}
47+
48+
public static function getSubscribedEvents(): array
49+
{
50+
return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments', 25]];
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 EventListener;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
17+
use Symfony\Component\HttpKernel\HttpKernelInterface;
18+
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
19+
use Symfony\Component\Security\Csrf\CsrfToken;
20+
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
21+
use Symfony\Component\Security\Http\EventListener\IsCsrfTokenValidAttributeListener;
22+
use Symfony\Component\Security\Http\Tests\Fixtures\IsCsrfTokenValidAttributeController;
23+
use Symfony\Component\Security\Http\Tests\Fixtures\IsCsrfTokenValidAttributeMethodsController;
24+
25+
class IsCsrfTokenValidAttributeListenerTest extends TestCase
26+
{
27+
public function testIsCsrfTokenValidCalledCorrectlyOnInvokableClass()
28+
{
29+
$request = new Request(request: ['_token' => 'bar']);
30+
31+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
32+
$csrfTokenManager->expects($this->once())
33+
->method('isTokenValid')
34+
->with(new CsrfToken('foo', 'bar'))
35+
->willReturn(true);
36+
37+
$event = new ControllerArgumentsEvent(
38+
$this->createMock(HttpKernelInterface::class),
39+
new IsCsrfTokenValidAttributeController(),
40+
[],
41+
$request,
42+
null
43+
);
44+
45+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
46+
$listener->onKernelControllerArguments($event);
47+
}
48+
49+
public function testNothingHappensWithNoConfig()
50+
{
51+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
52+
$csrfTokenManager->expects($this->never())
53+
->method('isTokenValid');
54+
55+
$event = new ControllerArgumentsEvent(
56+
$this->createMock(HttpKernelInterface::class),
57+
[new IsCsrfTokenValidAttributeMethodsController(), 'noAttribute'],
58+
[],
59+
new Request(),
60+
null
61+
);
62+
63+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
64+
$listener->onKernelControllerArguments($event);
65+
}
66+
67+
public function testIsCsrfTokenValidCalledCorrectly()
68+
{
69+
$request = new Request(request: ['_token' => 'bar']);
70+
71+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
72+
$csrfTokenManager->expects($this->once())
73+
->method('isTokenValid')
74+
->with(new CsrfToken('foo', 'bar'))
75+
->willReturn(true);
76+
77+
$event = new ControllerArgumentsEvent(
78+
$this->createMock(HttpKernelInterface::class),
79+
[new IsCsrfTokenValidAttributeMethodsController(), 'withDefaultTokenKey'],
80+
[],
81+
$request,
82+
null
83+
);
84+
85+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
86+
$listener->onKernelControllerArguments($event);
87+
}
88+
89+
public function testIsCsrfTokenValidCalledCorrectlyWithCustomTokenKey()
90+
{
91+
$request = new Request(request: ['my_token_key' => 'bar']);
92+
93+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
94+
$csrfTokenManager->expects($this->once())
95+
->method('isTokenValid')
96+
->with(new CsrfToken('foo', 'bar'))
97+
->willReturn(true);
98+
99+
$event = new ControllerArgumentsEvent(
100+
$this->createMock(HttpKernelInterface::class),
101+
[new IsCsrfTokenValidAttributeMethodsController(), 'withCustomTokenKey'],
102+
[],
103+
$request,
104+
null
105+
);
106+
107+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
108+
$listener->onKernelControllerArguments($event);
109+
}
110+
111+
public function testIsCsrfTokenValidCalledCorrectlyWithInvalidTokenKey()
112+
{
113+
$request = new Request(request: ['_token' => 'bar']);
114+
115+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
116+
$csrfTokenManager->expects($this->once())
117+
->method('isTokenValid')
118+
->with(new CsrfToken('foo', ''))
119+
->willReturn(true);
120+
121+
$event = new ControllerArgumentsEvent(
122+
$this->createMock(HttpKernelInterface::class),
123+
[new IsCsrfTokenValidAttributeMethodsController(), 'withInvalidTokenKey'],
124+
[],
125+
$request,
126+
null
127+
);
128+
129+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
130+
$listener->onKernelControllerArguments($event);
131+
}
132+
133+
public function testExceptionWhenInvalidToken()
134+
{
135+
$this->expectException(InvalidCsrfTokenException::class);
136+
137+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
138+
$csrfTokenManager->expects($this->once())
139+
->method('isTokenValid')
140+
->withAnyParameters()
141+
->willReturn(false);
142+
143+
$event = new ControllerArgumentsEvent(
144+
$this->createMock(HttpKernelInterface::class),
145+
[new IsCsrfTokenValidAttributeMethodsController(), 'withDefaultTokenKey'],
146+
[],
147+
new Request(),
148+
null
149+
);
150+
151+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
152+
$listener->onKernelControllerArguments($event);
153+
}
154+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Tests\Fixtures;
13+
14+
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
15+
16+
#[IsCsrfTokenValid('foo')]
17+
class IsCsrfTokenValidAttributeController
18+
{
19+
public function __invoke()
20+
{
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Tests\Fixtures;
13+
14+
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
15+
16+
class IsCsrfTokenValidAttributeMethodsController
17+
{
18+
public function noAttribute()
19+
{
20+
}
21+
22+
#[IsCsrfTokenValid('foo')]
23+
public function withDefaultTokenKey()
24+
{
25+
}
26+
27+
#[IsCsrfTokenValid('foo', tokenKey: 'my_token_key')]
28+
public function withCustomTokenKey()
29+
{
30+
}
31+
32+
#[IsCsrfTokenValid('foo', tokenKey: 'invalid_token_key')]
33+
public function withInvalidTokenKey()
34+
{
35+
}
36+
}

0 commit comments

Comments
 (0)