CsrfMiddlewareTest.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Unit\Http;
  4. use App\Http\CsrfMiddleware;
  5. use PHPUnit\Framework\TestCase;
  6. use Psr\Http\Message\ResponseInterface;
  7. use Psr\Http\Message\ServerRequestInterface;
  8. use Psr\Http\Server\RequestHandlerInterface;
  9. use Slim\Psr7\Factory\ResponseFactory;
  10. use Slim\Psr7\Factory\ServerRequestFactory;
  11. use Slim\Psr7\Factory\StreamFactory;
  12. final class CsrfMiddlewareTest extends TestCase
  13. {
  14. protected function setUp(): void
  15. {
  16. $_SESSION = [];
  17. }
  18. public function testGetGeneratesTokenAndPasses(): void
  19. {
  20. $mw = new CsrfMiddleware(new ResponseFactory());
  21. $request = (new ServerRequestFactory())->createServerRequest('GET', '/login');
  22. $response = $mw->process($request, $this->handler(static function (ServerRequestInterface $req): bool {
  23. return is_string($req->getAttribute(CsrfMiddleware::ATTR_TOKEN))
  24. && strlen((string) $req->getAttribute(CsrfMiddleware::ATTR_TOKEN)) === 64;
  25. }));
  26. self::assertSame(200, $response->getStatusCode());
  27. self::assertNotEmpty($_SESSION[CsrfMiddleware::SESSION_KEY]);
  28. }
  29. public function testPostWithoutTokenIs403(): void
  30. {
  31. $mw = new CsrfMiddleware(new ResponseFactory());
  32. $request = (new ServerRequestFactory())->createServerRequest('POST', '/login/local');
  33. $response = $mw->process($request, $this->handler(static fn () => true));
  34. self::assertSame(403, $response->getStatusCode());
  35. }
  36. public function testPostWithMatchingFormFieldPasses(): void
  37. {
  38. $mw = new CsrfMiddleware(new ResponseFactory());
  39. $_SESSION[CsrfMiddleware::SESSION_KEY] = 'fixed-token';
  40. $request = (new ServerRequestFactory())
  41. ->createServerRequest('POST', '/login/local')
  42. ->withParsedBody(['csrf_token' => 'fixed-token', 'username' => 'a']);
  43. $response = $mw->process($request, $this->handler(static fn () => true));
  44. self::assertSame(200, $response->getStatusCode());
  45. }
  46. public function testPostWithMatchingHeaderPasses(): void
  47. {
  48. $mw = new CsrfMiddleware(new ResponseFactory());
  49. $_SESSION[CsrfMiddleware::SESSION_KEY] = 'fixed-token';
  50. $request = (new ServerRequestFactory())
  51. ->createServerRequest('POST', '/api/x')
  52. ->withHeader('X-CSRF-Token', 'fixed-token');
  53. $response = $mw->process($request, $this->handler(static fn () => true));
  54. self::assertSame(200, $response->getStatusCode());
  55. }
  56. public function testPostWithWrongTokenIs403(): void
  57. {
  58. $mw = new CsrfMiddleware(new ResponseFactory());
  59. $_SESSION[CsrfMiddleware::SESSION_KEY] = 'fixed-token';
  60. $request = (new ServerRequestFactory())
  61. ->createServerRequest('POST', '/login/local')
  62. ->withParsedBody(['csrf_token' => 'wrong-token']);
  63. $response = $mw->process($request, $this->handler(static fn () => true));
  64. self::assertSame(403, $response->getStatusCode());
  65. }
  66. /**
  67. * @param callable(ServerRequestInterface): bool $assert
  68. */
  69. private function handler(callable $assert): RequestHandlerInterface
  70. {
  71. return new class ($assert) implements RequestHandlerInterface {
  72. /** @var callable(ServerRequestInterface): bool */
  73. private $assert;
  74. public function __construct(callable $assert)
  75. {
  76. $this->assert = $assert;
  77. }
  78. public function handle(ServerRequestInterface $request): ResponseInterface
  79. {
  80. $ok = ($this->assert)($request);
  81. $factory = new ResponseFactory();
  82. $response = $factory->createResponse($ok ? 200 : 418);
  83. $stream = (new StreamFactory())->createStream('OK');
  84. return $response->withBody($stream);
  85. }
  86. };
  87. }
  88. }