Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit695bd1a

Browse files
feature#54443 [Security] Add support for dynamic CSRF id with Expression in#[IsCsrfTokenValid] (yguedidi)
This PR was merged into the 7.1 branch.Discussion----------[Security] Add support for dynamic CSRF id with Expression in `#[IsCsrfTokenValid]`| Q | A| ------------- | ---| Branch? | 7.1| Bug fix? | no| New feature? | yes| Deprecations? | no| Issues | continuation of#52961 from Hackday| License | MITUse case is for example on a list page with delete action per item, and you want a CSRF token per item, so in the template you have something like the following:```twig{# in a loop over multiple posts #}<form action="{{ path('post_delete', {post: post.id}) }}" method="POST"> <input type="hidden" name="_token" value="{{ csrf_token('delete-post-' ~ post.id) }}"> ...</form>```The new feature will allow:```php#[IsCsrfTokenValid(new Expression('"delete-post-" ~ args["post"].id'))]public function delete(Request $request, Post $post): Response{ // ... delete the post}```Maybe this need more tests but need help identify which test cases are useful.Hope this can pass before the feature freezeCommits-------8f99ca5 Add support for dynamic CSRF id in IsCsrfTokenValid
2 parents4c1d8eb +8f99ca5 commit695bd1a

File tree

7 files changed

+85
-4
lines changed

7 files changed

+85
-4
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
useSymfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1515
useSymfony\Component\DependencyInjection\ContainerBuilder;
16+
useSymfony\Component\DependencyInjection\ContainerInterface;
1617
useSymfony\Component\DependencyInjection\Reference;
1718
useSymfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface;
1819
useSymfony\Component\Security\Http\EventListener\CsrfProtectionListener;
@@ -35,6 +36,10 @@ public function process(ContainerBuilder $container): void
3536

3637
privatefunctionregisterCsrfProtectionListener(ContainerBuilder$container):void
3738
{
39+
if (!$container->hasDefinition('cache.system')) {
40+
$container->removeDefinition('cache.security_is_csrf_token_valid_attribute_expression_language');
41+
}
42+
3843
if (!$container->has('security.authenticator.manager') || !$container->has('security.csrf.token_manager')) {
3944
return;
4045
}
@@ -45,6 +50,7 @@ private function registerCsrfProtectionListener(ContainerBuilder $container): vo
4550

4651
$container->register('controller.is_csrf_token_valid_attribute_listener', IsCsrfTokenValidAttributeListener::class)
4752
->addArgument(newReference('security.csrf.token_manager'))
53+
->addArgument(newReference('security.is_csrf_token_valid_attribute_expression_language', ContainerInterface::NULL_ON_INVALID_REFERENCE))
4854
->addTag('kernel.event_subscriber');
4955
}
5056

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public function load(array $configs, ContainerBuilder $container): void
123123
$container->removeDefinition('security.expression_language');
124124
$container->removeDefinition('security.access.expression_voter');
125125
$container->removeDefinition('security.is_granted_attribute_expression_language');
126+
$container->removeDefinition('security.is_csrf_token_valid_attribute_expression_language');
126127
}
127128

128129
if (!class_exists(PasswordHasherExtension::class)) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,5 +303,12 @@
303303
->set('cache.security_is_granted_attribute_expression_language')
304304
->parent('cache.system')
305305
->tag('cache.pool')
306+
307+
->set('security.is_csrf_token_valid_attribute_expression_language', BaseExpressionLanguage::class)
308+
->args([service('cache.security_is_csrf_token_valid_attribute_expression_language')->nullOnInvalid()])
309+
310+
->set('cache.security_is_csrf_token_valid_attribute_expression_language')
311+
->parent('cache.system')
312+
->tag('cache.pool')
306313
;
307314
};

‎src/Symfony/Component/Security/Http/Attribute/IsCsrfTokenValid.php‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111

1212
namespaceSymfony\Component\Security\Http\Attribute;
1313

14+
useSymfony\Component\ExpressionLanguage\Expression;
15+
1416
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)]
1517
finalclass IsCsrfTokenValid
1618
{
1719
publicfunction__construct(
1820
/**
19-
* Sets the id used when generating the token.
21+
* Sets the id, or an Expression evaluated to the id, used when generating the token.
2022
*/
21-
publicstring$id,
23+
publicstring|Expression$id,
2224

2325
/**
2426
* Sets the key of the request that contains the actual token value that should be validated.

‎src/Symfony/Component/Security/Http/EventListener/IsCsrfTokenValidAttributeListener.php‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
namespaceSymfony\Component\Security\Http\EventListener;
1313

1414
useSymfony\Component\EventDispatcher\EventSubscriberInterface;
15+
useSymfony\Component\ExpressionLanguage\Expression;
16+
useSymfony\Component\ExpressionLanguage\ExpressionLanguage;
17+
useSymfony\Component\HttpFoundation\Request;
1518
useSymfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
1619
useSymfony\Component\HttpKernel\KernelEvents;
1720
useSymfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
@@ -26,6 +29,7 @@ final class IsCsrfTokenValidAttributeListener implements EventSubscriberInterfac
2629
{
2730
publicfunction__construct(
2831
privatereadonlyCsrfTokenManagerInterface$csrfTokenManager,
32+
private ?ExpressionLanguage$expressionLanguage =null,
2933
) {
3034
}
3135

@@ -37,9 +41,12 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
3741
}
3842

3943
$request =$event->getRequest();
44+
$arguments =$event->getNamedArguments();
4045

4146
foreach ($attributesas$attribute) {
42-
if (!$this->csrfTokenManager->isTokenValid(newCsrfToken($attribute->id,$request->request->getString($attribute->tokenKey)))) {
47+
$id =$this->getTokenId($attribute->id,$request,$arguments);
48+
49+
if (!$this->csrfTokenManager->isTokenValid(newCsrfToken($id,$request->request->getString($attribute->tokenKey)))) {
4350
thrownewInvalidCsrfTokenException('Invalid CSRF token.');
4451
}
4552
}
@@ -49,4 +56,18 @@ public static function getSubscribedEvents(): array
4956
{
5057
return [KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelControllerArguments',25]];
5158
}
59+
60+
privatefunctiongetTokenId(string|Expression$id,Request$request,array$arguments):string
61+
{
62+
if (!$idinstanceof Expression) {
63+
return$id;
64+
}
65+
66+
$this->expressionLanguage ??=newExpressionLanguage();
67+
68+
return (string)$this->expressionLanguage->evaluate($id, [
69+
'request' =>$request,
70+
'args' =>$arguments,
71+
]);
72+
}
5273
}

‎src/Symfony/Component/Security/Http/Tests/EventListener/IsCsrfTokenValidAttributeListenerTest.php‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
* file that was distributed with this source code.
1010
*/
1111

12-
namespaceEventListener;
12+
namespaceSymfony\Component\Security\Http\Tests\EventListener;
1313

1414
usePHPUnit\Framework\TestCase;
15+
useSymfony\Component\ExpressionLanguage\Expression;
16+
useSymfony\Component\ExpressionLanguage\ExpressionLanguage;
1517
useSymfony\Component\HttpFoundation\Request;
1618
useSymfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
1719
useSymfony\Component\HttpKernel\HttpKernelInterface;
@@ -86,6 +88,37 @@ public function testIsCsrfTokenValidCalledCorrectly()
8688
$listener->onKernelControllerArguments($event);
8789
}
8890

91+
publicfunctiontestIsCsrfTokenValidCalledCorrectlyWithCustomExpressionId()
92+
{
93+
$request =newRequest(query: ['id' =>'123'], request: ['_token' =>'bar']);
94+
95+
$csrfTokenManager =$this->createMock(CsrfTokenManagerInterface::class);
96+
$csrfTokenManager->expects($this->once())
97+
->method('isTokenValid')
98+
->with(newCsrfToken('foo_123','bar'))
99+
->willReturn(true);
100+
101+
$expressionLanguage =$this->createMock(ExpressionLanguage::class);
102+
$expressionLanguage->expects($this->once())
103+
->method('evaluate')
104+
->with(newExpression('"foo_" ~ args.id'), [
105+
'args' => ['id' =>'123'],
106+
'request' =>$request,
107+
])
108+
->willReturn('foo_123');
109+
110+
$event =newControllerArgumentsEvent(
111+
$this->createMock(HttpKernelInterface::class),
112+
[newIsCsrfTokenValidAttributeMethodsController(),'withCustomExpressionId'],
113+
['123'],
114+
$request,
115+
null
116+
);
117+
118+
$listener =newIsCsrfTokenValidAttributeListener($csrfTokenManager,$expressionLanguage);
119+
$listener->onKernelControllerArguments($event);
120+
}
121+
89122
publicfunctiontestIsCsrfTokenValidCalledCorrectlyWithCustomTokenKey()
90123
{
91124
$request =newRequest(request: ['my_token_key' =>'bar']);

‎src/Symfony/Component/Security/Http/Tests/Fixtures/IsCsrfTokenValidAttributeMethodsController.php‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespaceSymfony\Component\Security\Http\Tests\Fixtures;
1313

14+
useSymfony\Component\ExpressionLanguage\Expression;
1415
useSymfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
1516

1617
class IsCsrfTokenValidAttributeMethodsController
@@ -24,6 +25,16 @@ public function withDefaultTokenKey()
2425
{
2526
}
2627

28+
#[IsCsrfTokenValid(newExpression('"foo_" ~ args.id'))]
29+
publicfunctionwithCustomExpressionId(string$id)
30+
{
31+
}
32+
33+
#[IsCsrfTokenValid(newExpression('"foo_" ~ args.slug'))]
34+
publicfunctionwithInvalidExpressionId(string$id)
35+
{
36+
}
37+
2738
#[IsCsrfTokenValid('foo', tokenKey:'my_token_key')]
2839
publicfunctionwithCustomTokenKey()
2940
{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp