CsrfErrorResponseMiddleware   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 120
c 0
b 0
f 0
wmc 11
lcom 1
cbo 6
ccs 32
cts 32
cp 1
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
B __invoke() 0 20 5
A checkCsrf() 0 18 4
A errorResponse() 0 11 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Chubbyphp\Csrf;
6
7
use Chubbyphp\Session\SessionInterface;
8
use Psr\Http\Message\ServerRequestInterface as Request;
9
use Psr\Http\Message\ResponseInterface as Response;
10
use Psr\Log\LoggerInterface;
11
use Psr\Log\NullLogger;
12
13
final class CsrfErrorResponseMiddleware
14
{
15
    /**
16
     * @var CsrfTokenGeneratorInterface
17
     */
18
    private $csrfTokenGenerator;
19
20
    /**
21
     * @var SessionInterface
22
     */
23
    private $session;
24
25
    const CSRF_KEY = 'csrf';
26
27
    /**
28
     * @var CsrfErrorHandlerInterface
29
     */
30
    private $errorResponseHandler;
31
32
    /**
33
     * @var LoggerInterface
34
     */
35
    private $logger;
36
37
    const EXCEPTION_STATUS = 424;
38
39
    const EXCEPTION_MISSING_IN_SESSION = 'Csrf token is missing within session';
40
    const EXCEPTION_MISSING_IN_BODY = 'Csrf token is missing within body';
41
    const EXCEPTION_IS_NOT_SAME = 'Csrf token within body is not the same as in session';
42
43
    /**
44
     * @param CsrfTokenGeneratorInterface $csrfTokenGenerator
45
     * @param SessionInterface            $session
46
     * @param CsrfErrorHandlerInterface   $errorResponseHandler
47
     * @param LoggerInterface|null        $logger
48
     */
49 6
    public function __construct(
50
        CsrfTokenGeneratorInterface $csrfTokenGenerator,
51
        SessionInterface $session,
52
        CsrfErrorHandlerInterface $errorResponseHandler,
53
        LoggerInterface $logger = null
54
    ) {
55 6
        $this->csrfTokenGenerator = $csrfTokenGenerator;
56 6
        $this->session = $session;
57 6
        $this->errorResponseHandler = $errorResponseHandler;
58 6
        $this->logger = $logger ?? new NullLogger();
59 6
    }
60
61
    /**
62
     * @param Request  $request
63
     * @param Response $response
64
     * @param callable $next
65
     *
66
     * @return Response
67
     */
68 6
    public function __invoke(Request $request, Response $response, callable $next = null)
69
    {
70 6
        if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
71 4
            $this->logger->info('csrf: check token');
72 4
            if (null !== $refererResponse = $this->checkCsrf($request, $response)) {
73 3
                return $refererResponse;
74
            }
75
        }
76
77 3
        if (!$this->session->has($request, self::CSRF_KEY)) {
78 2
            $this->logger->info('csrf: set token');
79 2
            $this->session->set($request, self::CSRF_KEY, $this->csrfTokenGenerator->generate());
80
        }
81
82 3
        if (null !== $next) {
83 1
            $response = $next($request, $response);
84
        }
85
86 3
        return $response;
87
    }
88
89
    /**
90
     * @param Request  $request
91
     * @param Response $response
92
     *
93
     * @return Response|null
94
     */
95 4
    private function checkCsrf(Request $request, Response $response)
96
    {
97 4
        if (!$this->session->has($request, self::CSRF_KEY)) {
98 1
            return $this->errorResponse($request, $response, self::EXCEPTION_MISSING_IN_SESSION);
99
        }
100
101 3
        $data = $request->getParsedBody();
102
103 3
        if (!isset($data[self::CSRF_KEY])) {
104 1
            return $this->errorResponse($request, $response, self::EXCEPTION_MISSING_IN_BODY);
105
        }
106
107 2
        if ($this->session->get($request, self::CSRF_KEY) !== $data[self::CSRF_KEY]) {
108 1
            return $this->errorResponse($request, $response, self::EXCEPTION_IS_NOT_SAME);
109
        }
110
111 1
        return null;
112
    }
113
114
    /**
115
     * @param Request  $request
116
     * @param Response $response
117
     * @param string   $reasonPhrase
118
     *
119
     * @return Response
120
     */
121 3
    private function errorResponse(Request $request, Response $response, string $reasonPhrase)
122
    {
123 3
        $this->logger->error(
124 3
            'csrf: error {status} {message}',
125 3
            ['status' => self::EXCEPTION_STATUS, 'message' => $reasonPhrase]
126
        );
127
128 3
        $this->session->set($request, self::CSRF_KEY, $this->csrfTokenGenerator->generate());
129
130 3
        return $this->errorResponseHandler->errorResponse($request, $response, self::EXCEPTION_STATUS, $reasonPhrase);
131
    }
132
}
133