Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #71: Deprecate CsrfMiddleware in favor of CsrfTokenMiddleware #71

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 2.1.2 under development

- no changes in this release.
- Chg #71: Deprecate `CsrfMiddleware` in favor of `CsrfTokenMiddleware` (@ev-gor)

## 2.1.1 May 08, 2024

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ composer require yiisoft/csrf

## General usage

In order to enable CSRF protection you need to add `CsrfMiddleware` to your main middleware stack.
In order to enable CSRF protection you need to add `CsrfTokenMiddleware` to your main middleware stack.
In Yii it is done by configuring `config/web/application.php`:

```php
Expand All @@ -48,7 +48,7 @@ return [
[
ErrorCatcher::class,
SessionMiddleware::class,
CsrfMiddleware::class, // <-- add this
CsrfTokenMiddleware::class, // <-- add this
Router::class,
]
);
Expand All @@ -74,7 +74,7 @@ You can change this behavior by implementing your own request handler:
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Yiisoft\Csrf\CsrfMiddleware;
use Yiisoft\Csrf\CsrfTokenMiddleware;

/**
* @var Psr\Http\Message\ResponseFactoryInterface $responseFactory
Expand All @@ -99,7 +99,7 @@ $failureHandler = new class ($responseFactory) implements RequestHandlerInterfac
}
};

$middleware = new CsrfMiddleware($responseFactory, $csrfToken, $failureHandler);
$middleware = new CsrfTokenMiddleware($responseFactory, $csrfToken, $failureHandler);
```

## CSRF Tokens
Expand Down
1 change: 1 addition & 0 deletions src/CsrfMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* PSR-15 middleware that takes care of token validation.
*
* @link https://www.php-fig.org/psr/psr-15/
* @deprecated Use the {@see CsrfTokenMiddleware} class instead.
*/
final class CsrfMiddleware implements MiddlewareInterface
{
Expand Down
109 changes: 109 additions & 0 deletions src/CsrfTokenMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Csrf;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Yiisoft\Http\Method;
use Yiisoft\Http\Status;

use function in_array;
use function is_string;

/**
* PSR-15 middleware that takes care of token validation.
*
* @link https://www.php-fig.org/psr/psr-15/
*/
final class CsrfTokenMiddleware implements MiddlewareInterface
{
public const PARAMETER_NAME = '_csrf';
public const HEADER_NAME = 'X-CSRF-Token';

private string $parameterName = self::PARAMETER_NAME;
private string $headerName = self::HEADER_NAME;

private ResponseFactoryInterface $responseFactory;
private CsrfTokenInterface $token;
private ?RequestHandlerInterface $failureHandler;

public function __construct(
ResponseFactoryInterface $responseFactory,
CsrfTokenInterface $token,
RequestHandlerInterface $failureHandler = null
) {
$this->responseFactory = $responseFactory;
$this->token = $token;
$this->failureHandler = $failureHandler;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($this->validateCsrfToken($request)) {
return $handler->handle($request);
}

if ($this->failureHandler !== null) {
return $this->failureHandler->handle($request);
}

$response = $this->responseFactory->createResponse(Status::UNPROCESSABLE_ENTITY);
$response
->getBody()
->write(Status::TEXTS[Status::UNPROCESSABLE_ENTITY]);
return $response;
}

public function withParameterName(string $name): self
{
$new = clone $this;
$new->parameterName = $name;
return $new;
}

public function withHeaderName(string $name): self
{
$new = clone $this;
$new->headerName = $name;
return $new;
}

public function getParameterName(): string
{
return $this->parameterName;
}

public function getHeaderName(): string
{
return $this->headerName;
}

private function validateCsrfToken(ServerRequestInterface $request): bool
{
if (in_array($request->getMethod(), [Method::GET, Method::HEAD, Method::OPTIONS], true)) {
return true;
}

$token = $this->getTokenFromRequest($request);

return !empty($token) && $this->token->validate($token);
}

private function getTokenFromRequest(ServerRequestInterface $request): ?string
{
$parsedBody = $request->getParsedBody();

$token = $parsedBody[$this->parameterName] ?? null;
if (empty($token)) {
$headers = $request->getHeader($this->headerName);
$token = reset($headers);
}

return is_string($token) ? $token : null;
}
}
10 changes: 5 additions & 5 deletions tests/CsrfMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Nyholm\Psr7\Factory\Psr17Factory;
use PHPUnit\Framework\TestCase;
use Yiisoft\Csrf\CsrfMiddleware;
use Yiisoft\Csrf\CsrfTokenMiddleware;
use Yiisoft\Csrf\Synchronizer\Generator\RandomCsrfTokenGenerator;
use Yiisoft\Csrf\Synchronizer\SynchronizerCsrfToken;
use Yiisoft\Csrf\Tests\Synchronizer\Storage\MockCsrfTokenStorage;
Expand All @@ -16,7 +16,7 @@ final class CsrfMiddlewareTest extends TestCase
public function testDefaultParameterName(): void
{
$middleware = $this->createMiddleware();
$this->assertSame(CsrfMiddleware::PARAMETER_NAME, $middleware->getParameterName());
$this->assertSame(CsrfTokenMiddleware::PARAMETER_NAME, $middleware->getParameterName());
}

public function testGetParameterName(): void
Expand All @@ -30,7 +30,7 @@ public function testGetParameterName(): void
public function testDefaultHeaderName(): void
{
$middleware = $this->createMiddleware();
$this->assertSame(CsrfMiddleware::HEADER_NAME, $middleware->getHeaderName());
$this->assertSame(CsrfTokenMiddleware::HEADER_NAME, $middleware->getHeaderName());
}

public function testGetHeaderName(): void
Expand All @@ -48,9 +48,9 @@ public function testImmutability(): void
$this->assertNotSame($original, $original->withParameterName('csrf'));
}

private function createMiddleware(): CsrfMiddleware
private function createMiddleware(): CsrfTokenMiddleware
{
return new CsrfMiddleware(
return new CsrfTokenMiddleware(
new Psr17Factory(),
new SynchronizerCsrfToken(
new RandomCsrfTokenGenerator(),
Expand Down
61 changes: 61 additions & 0 deletions tests/DeprecatedCsrfMiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Csrf\Tests;

use Nyholm\Psr7\Factory\Psr17Factory;
use PHPUnit\Framework\TestCase;
use Yiisoft\Csrf\CsrfMiddleware;
use Yiisoft\Csrf\Synchronizer\Generator\RandomCsrfTokenGenerator;
use Yiisoft\Csrf\Synchronizer\SynchronizerCsrfToken;
use Yiisoft\Csrf\Tests\Synchronizer\Storage\MockCsrfTokenStorage;

final class DeprecatedCsrfMiddlewareTest extends TestCase
{
public function testDefaultParameterName(): void
{
$middleware = $this->createMiddleware();
$this->assertSame(CsrfMiddleware::PARAMETER_NAME, $middleware->getParameterName());
}

public function testGetParameterName(): void
{
$middleware = $this
->createMiddleware()
->withParameterName('my-csrf');
$this->assertSame('my-csrf', $middleware->getParameterName());
}

public function testDefaultHeaderName(): void
{
$middleware = $this->createMiddleware();
$this->assertSame(CsrfMiddleware::HEADER_NAME, $middleware->getHeaderName());
}

public function testGetHeaderName(): void
{
$middleware = $this
->createMiddleware()
->withHeaderName('MY-CSRF');
$this->assertSame('MY-CSRF', $middleware->getHeaderName());
}

public function testImmutability(): void
{
$original = $this->createMiddleware();
$this->assertNotSame($original, $original->withHeaderName('csrf'));
$this->assertNotSame($original, $original->withParameterName('csrf'));
}

private function createMiddleware(): CsrfMiddleware
{
return new CsrfMiddleware(
new Psr17Factory(),
new SynchronizerCsrfToken(
new RandomCsrfTokenGenerator(),
new MockCsrfTokenStorage()
)
);
}
}
Loading