testWillSucceedIfANonSafeRequestIsProvidedWithAValidTokenWithNextMiddleware()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 38
rs 9.312
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PSR7CsrfTest;
6
7
use Lcobucci\JWT\Builder;
8
use Lcobucci\JWT\Parser;
9
use Lcobucci\JWT\Signer;
10
use PHPUnit\Framework\TestCase;
11
use Psr\Http\Message\ResponseInterface;
12
use Psr\Http\Message\ServerRequestInterface;
13
use Psr\Http\Server\RequestHandlerInterface;
14
use PSR7Csrf\CSRFCheckerMiddleware;
15
use PSR7Csrf\Exception\SessionAttributeNotFoundException;
16
use PSR7Csrf\HttpMethod\IsSafeHttpRequestInterface;
17
use PSR7Csrf\RequestParameter\ExtractCSRFParameterInterface;
18
use PSR7Csrf\Session\ExtractUniqueKeyFromSessionInterface;
19
use PSR7Sessions\Storageless\Session\SessionInterface;
20
use stdClass;
21
22
/**
23
 * @covers \PSR7Csrf\CSRFCheckerMiddleware
24
 */
25
final class CSRFCheckerMiddlewareTest extends TestCase
26
{
27
    /**
28
     * @var Signer
29
     */
30
    private $signer;
31
32
    /**
33
     * @var Parser
34
     */
35
    private $tokenParser;
36
37
    /**
38
     * @var IsSafeHttpRequestInterface|\PHPUnit_Framework_MockObject_MockObject
39
     */
40
    private $isSafeHttpRequest;
41
42
    /**
43
     * @var ExtractUniqueKeyFromSessionInterface|\PHPUnit_Framework_MockObject_MockObject
44
     */
45
    private $extractUniqueKeyFromSession;
46
47
    /**
48
     * @var ExtractCSRFParameterInterface|\PHPUnit_Framework_MockObject_MockObject
49
     */
50
    private $extractCSRFParameter;
51
52
    /**
53
     * @var ServerRequestInterface|\PHPUnit_Framework_MockObject_MockObject
54
     */
55
    private $request;
56
57
    /**
58
     * @var ResponseInterface|\PHPUnit_Framework_MockObject_MockObject
59
     */
60
    private $response;
61
62
    /**
63
     * @var SessionInterface|\PHPUnit_Framework_MockObject_MockObject
64
     */
65
    private $session;
66
67
    /**
68
     * @var string
69
     */
70
    private $sessionAttribute;
71
72
    /**
73
     * @var RequestHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
74
     */
75
    private $nextMiddleware;
76
77
    /**
78
     * @var CSRFCheckerMiddleware
79
     */
80
    private $middleware;
81
82
    /**
83
     * @var ResponseInterface
84
     */
85
    private $faultyResponse;
86
87
    /**
88
     * {@inheritDoc}
89
     */
90
    protected function setUp()
91
    {
92
        parent::setUp();
93
94
        $this->signer                      = new Signer\Hmac\Sha256();
95
        $this->tokenParser                 = new Parser();
96
        $this->isSafeHttpRequest           = $this->createMock(IsSafeHttpRequestInterface::class);
97
        $this->extractUniqueKeyFromSession = $this->createMock(ExtractUniqueKeyFromSessionInterface::class);
98
        $this->extractCSRFParameter        = $this->createMock(ExtractCSRFParameterInterface::class);
99
        $this->request                     = $this->createMock(ServerRequestInterface::class);
100
        $this->response                    = $this->createMock(ResponseInterface::class);
101
        $this->session                     = $this->createMock(SessionInterface::class);
102
        $this->sessionAttribute            = uniqid('session', true);
103
        $this->nextMiddleware              = $this->createMock(RequestHandlerInterface::class);
104
        $this->faultyResponse              = $this->createMock(ResponseInterface::class);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->createMock(\Psr\H...sponseInterface::class) of type object<PHPUnit\Framework\MockObject\MockObject> is incompatible with the declared type object<Psr\Http\Message\ResponseInterface> of property $faultyResponse.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
105
        $this->middleware                  = new CSRFCheckerMiddleware(
106
            $this->isSafeHttpRequest,
0 ignored issues
show
Documentation introduced by
$this->isSafeHttpRequest is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a object<PSR7Csrf\HttpMeth...feHttpRequestInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
107
            $this->extractUniqueKeyFromSession,
0 ignored issues
show
Documentation introduced by
$this->extractUniqueKeyFromSession is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a object<PSR7Csrf\Session\...eyFromSessionInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
108
            $this->extractCSRFParameter,
0 ignored issues
show
Documentation introduced by
$this->extractCSRFParameter is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a object<PSR7Csrf\RequestP...CSRFParameterInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
109
            $this->tokenParser,
110
            $this->signer,
111
            $this->sessionAttribute,
112
            $this->faultyResponse
0 ignored issues
show
Documentation introduced by
$this->faultyResponse is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a object<Psr\Http\Message\ResponseInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
113
        );
114
    }
115
116
    public function testWillIgnoreSafeRequestsWithNoNextMiddleware()
117
    {
118
        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(true);
119
120
        $this
121
            ->nextMiddleware
122
            ->expects(self::once())
123
            ->method('handle')
124
            ->with($this->request)
125
            ->willReturn($this->response);
126
127
        self::assertSame($this->response, $this->middleware->process($this->request, $this->nextMiddleware));
128
    }
129
130
    public function testWillSucceedIfANonSafeRequestIsProvidedWithAValidTokenWithNextMiddleware()
131
    {
132
        $secret          = uniqid('secret', true);
133
        $validToken      = (new Builder())
134
            ->sign($this->signer, $secret)
135
            ->getToken();
136
137
        $this
138
            ->nextMiddleware
139
            ->expects(self::once())
140
            ->method('handle')
141
            ->with($this->request)
142
            ->willReturn($this->response);
143
        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);
144
        $this
145
            ->extractUniqueKeyFromSession
146
            ->expects(self::any())
147
            ->method('__invoke')
148
            ->with($this->session)
149
            ->willReturn($secret);
150
        $this
151
            ->extractCSRFParameter
152
            ->expects(self::any())
153
            ->method('__invoke')
154
            ->with($this->request)
155
            ->willReturn((string) $validToken);
156
        $this
157
            ->request
158
            ->expects(self::any())
159
            ->method('getAttribute')
160
            ->with($this->sessionAttribute)
161
            ->willReturn($this->session);
162
163
        self::assertSame(
164
            $this->response,
165
            $this->middleware->process($this->request, $this->nextMiddleware)
166
        );
167
    }
168
169
    public function testNonMatchingSignedTokensAreRejected()
170
    {
171
        $secret          = uniqid('secret', true);
172
        $validToken      = (new Builder())
173
            ->sign($this->signer, uniqid('wrongSecret', true))
174
            ->getToken();
175
176
        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);
177
        $this
178
            ->extractUniqueKeyFromSession
179
            ->expects(self::any())
180
            ->method('__invoke')
181
            ->with($this->session)
182
            ->willReturn($secret);
183
        $this
184
            ->extractCSRFParameter
185
            ->expects(self::any())
186
            ->method('__invoke')
187
            ->with($this->request)
188
            ->willReturn((string) $validToken);
189
        $this
190
            ->request
191
            ->expects(self::any())
192
            ->method('getAttribute')
193
            ->with($this->sessionAttribute)
194
            ->willReturn($this->session);
195
196
        $this->assertFaultyResponse();
197
    }
198
199
    public function testUnsignedTokensAreRejected()
200
    {
201
        $secret     = uniqid('secret', true);
202
        $validToken = (new Builder())->getToken();
203
204
        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);
205
        $this
206
            ->extractUniqueKeyFromSession
207
            ->expects(self::any())
208
            ->method('__invoke')
209
            ->with($this->session)
210
            ->willReturn($secret);
211
        $this
212
            ->extractCSRFParameter
213
            ->expects(self::any())
214
            ->method('__invoke')
215
            ->with($this->request)
216
            ->willReturn((string) $validToken);
217
        $this
218
            ->request
219
            ->expects(self::any())
220
            ->method('getAttribute')
221
            ->with($this->sessionAttribute)
222
            ->willReturn($this->session);
223
224
        $this->assertFaultyResponse();
225
    }
226
227
    public function testExpiredSignedTokensAreRejected()
228
    {
229
        $secret          = uniqid('secret', true);
230
        $validToken      = (new Builder())
231
            ->setExpiration(time() - 3600)
232
            ->sign($this->signer, $secret)
233
            ->getToken();
234
235
        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);
236
        $this
237
            ->extractUniqueKeyFromSession
238
            ->expects(self::any())
239
            ->method('__invoke')
240
            ->with($this->session)
241
            ->willReturn($secret);
242
        $this
243
            ->extractCSRFParameter
244
            ->expects(self::any())
245
            ->method('__invoke')
246
            ->with($this->request)
247
            ->willReturn((string) $validToken);
248
        $this
249
            ->request
250
            ->expects(self::any())
251
            ->method('getAttribute')
252
            ->with($this->sessionAttribute)
253
            ->willReturn($this->session);
254
255
        $this->assertFaultyResponse();
256
    }
257
258
    public function testMalformedTokensShouldBeRejected()
259
    {
260
        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);
261
        $this
262
            ->extractUniqueKeyFromSession
263
            ->expects(self::any())
264
            ->method('__invoke')
265
            ->with($this->session)
266
            ->willReturn(uniqid('secret', true));
267
        $this
268
            ->extractCSRFParameter
269
            ->expects(self::any())
270
            ->method('__invoke')
271
            ->with($this->request)
272
            ->willReturn('yadda yadda invalid bs');
273
        $this
274
            ->request
275
            ->expects(self::any())
276
            ->method('getAttribute')
277
            ->with($this->sessionAttribute)
278
            ->willReturn($this->session);
279
280
        $this->assertFaultyResponse();
281
    }
282
283
    public function testWillFailIfARequestDoesNotIncludeASession()
284
    {
285
        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);
286
        $this
287
            ->extractCSRFParameter
288
            ->expects(self::any())
289
            ->method('__invoke')
290
            ->with($this->request)
291
            ->willReturn((new Builder())->getToken());
292
        $this
293
            ->request
294
            ->expects(self::any())
295
            ->method('getAttribute')
296
            ->with($this->sessionAttribute)
297
            ->willReturn(new stdClass());
298
        $this
299
            ->request
300
            ->expects(self::any())
301
            ->method('getAttributes')
302
            ->willReturn([]);
303
304
        $this->expectException(SessionAttributeNotFoundException::class);
305
306
        $this->middleware->process($this->request, $this->nextMiddleware);
307
    }
308
309
    private function assertFaultyResponse() : void
310
    {
311
        $this->nextMiddleware->expects(self::never())->method('handle');
312
313
        self::assertSame($this->faultyResponse, $this->middleware->process($this->request, $this->nextMiddleware));
314
    }
315
}
316