Skip to content

Commit 9fefa7a

Browse files
Oviglonicolas-grekas
authored andcommitted
[Security] Add methods param in IsCsrfTokenValid attribute
1 parent 13ba7cb commit 9fefa7a

File tree

4 files changed

+163
-0
lines changed

4 files changed

+163
-0
lines changed

Attribute/IsCsrfTokenValid.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public function __construct(
2626
* Sets the key of the request that contains the actual token value that should be validated.
2727
*/
2828
public ?string $tokenKey = '_token',
29+
30+
/**
31+
* Sets the available http methods that can be used to validate the token.
32+
* If not set, the token will be validated for all methods.
33+
*/
34+
public array|string $methods = [],
2935
) {
3036
}
3137
}

EventListener/IsCsrfTokenValidAttributeListener.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
4545

4646
foreach ($attributes as $attribute) {
4747
$id = $this->getTokenId($attribute->id, $request, $arguments);
48+
$methods = \array_map('strtoupper', (array) $attribute->methods);
49+
50+
if ($methods && !\in_array($request->getMethod(), $methods, true)) {
51+
continue;
52+
}
4853

4954
if (!$this->csrfTokenManager->isTokenValid(new CsrfToken($id, $request->getPayload()->getString($attribute->tokenKey)))) {
5055
throw new InvalidCsrfTokenException('Invalid CSRF token.');

Tests/EventListener/IsCsrfTokenValidAttributeListenerTest.php

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,141 @@ public function testExceptionWhenInvalidToken()
206206
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
207207
$listener->onKernelControllerArguments($event);
208208
}
209+
210+
public function testIsCsrfTokenValidCalledCorrectlyWithDeleteMethod()
211+
{
212+
$request = new Request(request: ['_token' => 'bar']);
213+
$request->setMethod('DELETE');
214+
215+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
216+
$csrfTokenManager->expects($this->once())
217+
->method('isTokenValid')
218+
->with(new CsrfToken('foo', 'bar'))
219+
->willReturn(true);
220+
221+
$event = new ControllerArgumentsEvent(
222+
$this->createMock(HttpKernelInterface::class),
223+
[new IsCsrfTokenValidAttributeMethodsController(), 'withDeleteMethod'],
224+
[],
225+
$request,
226+
null
227+
);
228+
229+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
230+
$listener->onKernelControllerArguments($event);
231+
}
232+
233+
public function testIsCsrfTokenValidIgnoredWithNonMatchingMethod()
234+
{
235+
$request = new Request(request: ['_token' => 'bar']);
236+
$request->setMethod('POST');
237+
238+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
239+
$csrfTokenManager->expects($this->never())
240+
->method('isTokenValid')
241+
->with(new CsrfToken('foo', 'bar'));
242+
243+
$event = new ControllerArgumentsEvent(
244+
$this->createMock(HttpKernelInterface::class),
245+
[new IsCsrfTokenValidAttributeMethodsController(), 'withDeleteMethod'],
246+
[],
247+
$request,
248+
null
249+
);
250+
251+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
252+
$listener->onKernelControllerArguments($event);
253+
}
254+
255+
public function testIsCsrfTokenValidCalledCorrectlyWithGetOrPostMethodWithGetMethod()
256+
{
257+
$request = new Request(request: ['_token' => 'bar']);
258+
$request->setMethod('GET');
259+
260+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
261+
$csrfTokenManager->expects($this->once())
262+
->method('isTokenValid')
263+
->with(new CsrfToken('foo', 'bar'))
264+
->willReturn(true);
265+
266+
$event = new ControllerArgumentsEvent(
267+
$this->createMock(HttpKernelInterface::class),
268+
[new IsCsrfTokenValidAttributeMethodsController(), 'withGetOrPostMethod'],
269+
[],
270+
$request,
271+
null
272+
);
273+
274+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
275+
$listener->onKernelControllerArguments($event);
276+
}
277+
278+
public function testIsCsrfTokenValidNoIgnoredWithGetOrPostMethodWithPutMethod()
279+
{
280+
$request = new Request(request: ['_token' => 'bar']);
281+
$request->setMethod('PUT');
282+
283+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
284+
$csrfTokenManager->expects($this->never())
285+
->method('isTokenValid')
286+
->with(new CsrfToken('foo', 'bar'));
287+
288+
$event = new ControllerArgumentsEvent(
289+
$this->createMock(HttpKernelInterface::class),
290+
[new IsCsrfTokenValidAttributeMethodsController(), 'withGetOrPostMethod'],
291+
[],
292+
$request,
293+
null
294+
);
295+
296+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
297+
$listener->onKernelControllerArguments($event);
298+
}
299+
300+
public function testIsCsrfTokenValidCalledCorrectlyWithInvalidTokenKeyAndPostMethod()
301+
{
302+
$this->expectException(InvalidCsrfTokenException::class);
303+
304+
$request = new Request(request: ['_token' => 'bar']);
305+
$request->setMethod('POST');
306+
307+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
308+
$csrfTokenManager->expects($this->once())
309+
->method('isTokenValid')
310+
->withAnyParameters()
311+
->willReturn(false);
312+
313+
$event = new ControllerArgumentsEvent(
314+
$this->createMock(HttpKernelInterface::class),
315+
[new IsCsrfTokenValidAttributeMethodsController(), 'withPostMethodAndInvalidTokenKey'],
316+
[],
317+
$request,
318+
null
319+
);
320+
321+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
322+
$listener->onKernelControllerArguments($event);
323+
}
324+
325+
public function testIsCsrfTokenValidIgnoredWithInvalidTokenKeyAndUnavailableMethod()
326+
{
327+
$request = new Request(request: ['_token' => 'bar']);
328+
$request->setMethod('PUT');
329+
330+
$csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
331+
$csrfTokenManager->expects($this->never())
332+
->method('isTokenValid')
333+
->withAnyParameters();
334+
335+
$event = new ControllerArgumentsEvent(
336+
$this->createMock(HttpKernelInterface::class),
337+
[new IsCsrfTokenValidAttributeMethodsController(), 'withPostMethodAndInvalidTokenKey'],
338+
[],
339+
$request,
340+
null
341+
);
342+
343+
$listener = new IsCsrfTokenValidAttributeListener($csrfTokenManager);
344+
$listener->onKernelControllerArguments($event);
345+
}
209346
}

Tests/Fixtures/IsCsrfTokenValidAttributeMethodsController.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,19 @@ public function withCustomTokenKey()
4444
public function withInvalidTokenKey()
4545
{
4646
}
47+
48+
#[IsCsrfTokenValid('foo', methods: 'DELETE')]
49+
public function withDeleteMethod()
50+
{
51+
}
52+
53+
#[IsCsrfTokenValid('foo', methods: ['GET', 'POST'])]
54+
public function withGetOrPostMethod()
55+
{
56+
}
57+
58+
#[IsCsrfTokenValid('foo', tokenKey: 'invalid_token_key', methods: ['POST'])]
59+
public function withPostMethodAndInvalidTokenKey()
60+
{
61+
}
4762
}

0 commit comments

Comments
 (0)