Passed
Push — master ( 1e2fd6...5e7c1f )
by Marco
58s queued 22s
created

testMutableCookieWillNotBeUsed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 9.52
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license.
17
 */
18
19
declare(strict_types=1);
20
21
namespace PSR7SessionsTest\Storageless\Http;
22
23
use DateTime;
24
use DateTimeImmutable;
25
use Dflydev\FigCookies\FigResponseCookies;
26
use Dflydev\FigCookies\Modifier\SameSite;
27
use Dflydev\FigCookies\SetCookie;
28
use InvalidArgumentException;
29
use Lcobucci\Clock\FrozenClock;
30
use Lcobucci\Clock\SystemClock;
31
use Lcobucci\JWT\Builder;
32
use Lcobucci\JWT\Parser;
33
use Lcobucci\JWT\Signer;
34
use Lcobucci\JWT\Signer\Hmac\Sha256;
35
use OutOfBoundsException;
36
use PHPUnit\Framework\TestCase;
37
use Psr\Http\Message\RequestInterface;
38
use Psr\Http\Message\ResponseInterface;
39
use Psr\Http\Message\ServerRequestInterface;
40
use Psr\Http\Server\RequestHandlerInterface;
41
use PSR7Sessions\Storageless\Http\SessionMiddleware;
42
use PSR7Sessions\Storageless\Session\DefaultSessionData;
43
use PSR7Sessions\Storageless\Session\SessionInterface;
44
use PSR7SessionsTest\Storageless\Asset\MutableBadCookie;
45
use ReflectionProperty;
46
use Zend\Diactoros\Response;
47
use Zend\Diactoros\ServerRequest;
48
use function assert;
49
use function file_get_contents;
50
use function random_int;
51
use function time;
52
use function uniqid;
53
54
final class SessionMiddlewareTest extends TestCase
55
{
56
    public function testFromSymmetricKeyDefaultsUsesASecureCookie() : void
57
    {
58
        $response = SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)
59
            ->process(new ServerRequest(), $this->writingMiddleware());
60
61
        $cookie = $this->getCookie($response);
62
63
        self::assertTrue($cookie->getSecure());
64
        self::assertTrue($cookie->getHttpOnly());
65
    }
66
67
    public function testFromAsymmetricKeyDefaultsUsesASecureCookie() : void
68
    {
69
        $response = SessionMiddleware::fromAsymmetricKeyDefaults(
70
            self::privateKey(),
71
            self::publicKey(),
72
            200
73
        )
74
            ->process(new ServerRequest(), $this->writingMiddleware());
75
76
        $cookie = $this->getCookie($response);
77
78
        self::assertTrue($cookie->getSecure());
79
        self::assertTrue($cookie->getHttpOnly());
80
    }
81
82
    /**
83
     * @dataProvider validMiddlewaresProvider
84
     */
85
    public function testSkipsInjectingSessionCookieOnEmptyContainer(SessionMiddleware $middleware) : void
86
    {
87
        $response = $this->ensureSameResponse($middleware, new ServerRequest(), $this->emptyValidationMiddleware());
88
89
        self::assertNull($this->getCookie($response)->getValue());
90
    }
91
92
    /**
93
     * @dataProvider validMiddlewaresProvider
94
     */
95
    public function testExtractsSessionContainerFromEmptyRequest(SessionMiddleware $middleware) : void
96
    {
97
        $this->ensureSameResponse($middleware, new ServerRequest(), $this->emptyValidationMiddleware());
98
    }
99
100
    /**
101
     * @dataProvider validMiddlewaresProvider
102
     */
103
    public function testInjectsSessionInResponseCookies(SessionMiddleware $middleware) : void
104
    {
105
        $initialResponse = new Response();
106
        $response        = $middleware->process(new ServerRequest(), $this->writingMiddleware());
107
108
        self::assertNotSame($initialResponse, $response);
109
        self::assertEmpty($this->getCookie($response, 'non-existing')->getValue());
110
111
        $token = $this->getCookie($response)->getValue();
112
113
        self::assertIsString($token);
114
        self::assertEquals((object) ['foo' => 'bar'], (new Parser())->parse($token)->getClaim('session-data'));
115
    }
116
117
    /**
118
     * @dataProvider validMiddlewaresProvider
119
     */
120
    public function testSessionContainerCanBeReusedOverMultipleRequests(SessionMiddleware $middleware) : void
121
    {
122
        $sessionValue = uniqid('', true);
123
124
        $checkingMiddleware = $this->fakeDelegate(
125
            static function (ServerRequestInterface $request) use ($sessionValue) {
126
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
127
                assert($session instanceof SessionInterface);
128
129
                self::assertSame($sessionValue, $session->get('foo'));
130
                self::assertFalse($session->hasChanged());
131
132
                $session->set('foo', $sessionValue . 'changed');
133
134
                self::assertTrue(
135
                    $session->hasChanged(),
136
                    'ensuring that the cookie is sent again: '
137
                    . 'non-modified session containers are not to be re-serialized into a token'
138
                );
139
140
                return new Response();
141
            }
142
        );
143
144
        $firstResponse = $middleware->process(new ServerRequest(), $this->writingMiddleware($sessionValue));
145
146
        $response = $middleware->process(
147
            $this->requestWithResponseCookies($firstResponse),
148
            $checkingMiddleware
149
        );
150
151
        self::assertNotSame($response, $firstResponse);
152
    }
153
154
    /**
155
     * @dataProvider validMiddlewaresProvider
156
     */
157
    public function testSessionContainerCanBeCreatedEvenIfTokenDataIsMalformed(SessionMiddleware $middleware) : void
158
    {
159
        $sessionValue = uniqid('not valid session data', true);
160
161
        $checkingMiddleware = $this->fakeDelegate(
162
            static function (ServerRequestInterface $request) use ($sessionValue) {
163
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
164
                assert($session instanceof SessionInterface);
165
166
                self::assertSame($sessionValue, $session->get('scalar'));
167
                self::assertFalse($session->hasChanged());
168
169
                return new Response();
170
            }
171
        );
172
173
        $this->createTokenWithCustomClaim(
174
            $middleware,
175
            new DateTime('-1 day'),
176
            new DateTime('+1 day'),
177
            'not valid session data'
178
        );
179
180
        $middleware->process(
181
            (new ServerRequest())
182
                ->withCookieParams([
183
                    SessionMiddleware::DEFAULT_COOKIE => $this->createTokenWithCustomClaim(
184
                        $middleware,
185
                        new DateTime('-1 day'),
186
                        new DateTime('+1 day'),
187
                        $sessionValue
188
                    ),
189
                ]),
190
            $checkingMiddleware
191
        );
192
    }
193
194
    /**
195
     * @dataProvider validMiddlewaresProvider
196
     */
197
    public function testWillIgnoreRequestsWithExpiredTokens(SessionMiddleware $middleware) : void
198
    {
199
        $expiredToken = (new ServerRequest())
200
            ->withCookieParams([
201
                SessionMiddleware::DEFAULT_COOKIE => $this->createToken(
202
                    $middleware,
203
                    new DateTime('-1 day'),
204
                    new DateTime('-2 day')
205
                ),
206
            ]);
207
208
        $this->ensureSameResponse($middleware, $expiredToken, $this->emptyValidationMiddleware());
209
    }
210
211
    /**
212
     * @dataProvider validMiddlewaresProvider
213
     */
214
    public function testWillIgnoreRequestsWithTokensFromFuture(SessionMiddleware $middleware) : void
215
    {
216
        $tokenInFuture = (new ServerRequest())
217
            ->withCookieParams([
218
                SessionMiddleware::DEFAULT_COOKIE => $this->createToken(
219
                    $middleware,
220
                    new DateTime('+1 day'),
221
                    new DateTime('-2 day')
222
                ),
223
            ]);
224
225
        $this->ensureSameResponse($middleware, $tokenInFuture, $this->emptyValidationMiddleware());
226
    }
227
228
    /**
229
     * @dataProvider validMiddlewaresProvider
230
     */
231
    public function testWillIgnoreUnSignedTokens(SessionMiddleware $middleware) : void
232
    {
233
        $unsignedToken = (new ServerRequest())
234
            ->withCookieParams([
235
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setIssuedAt() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setExpiration() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::set() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
236
                    ->setIssuedAt((new DateTime('-1 day'))->getTimestamp())
237
                    ->setExpiration((new DateTime('+1 day'))->getTimestamp())
238
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
239
                    ->getToken(),
240
            ]);
241
242
        $this->ensureSameResponse($middleware, $unsignedToken, $this->emptyValidationMiddleware());
243
    }
244
245
    /**
246
     * @dataProvider validMiddlewaresProvider
247
     */
248
    public function testWillNotRefreshSignedTokensWithoutIssuedAt(SessionMiddleware $middleware) : void
249
    {
250
        $unsignedToken = (new ServerRequest())
251
            ->withCookieParams([
252
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setExpiration() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::set() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::sign() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
253
                    ->setExpiration((new DateTime('+1 day'))->getTimestamp())
254
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
255
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
256
                    ->getToken(),
257
            ]);
258
259
        $this->ensureSameResponse($middleware, $unsignedToken);
260
    }
261
262
    public function testWillRefreshTokenWithIssuedAtExactlyAtTokenRefreshTimeThreshold() : void
263
    {
264
        // forcing ourselves to think of time as a mutable value:
265
        $time = time() + random_int(-100, +100);
266
267
        $clock = new FrozenClock(new DateTimeImmutable('@' . $time));
268
269
        $middleware = new SessionMiddleware(
270
            new Sha256(),
271
            'foo',
272
            'foo',
273
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
274
            new Parser(),
275
            1000,
276
            $clock,
277
            100
278
        );
279
280
        $requestWithTokenIssuedInThePast = (new ServerRequest())
281
            ->withCookieParams([
282
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setExpiration() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setIssuedAt() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::set() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::sign() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
283
                    ->setExpiration($time + 10000)
284
                    ->setIssuedAt($time - 100)
285
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
286
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
287
                    ->getToken(),
288
            ]);
289
290
        $tokenString = $this
291
            ->getCookie($middleware->process($requestWithTokenIssuedInThePast, $this->fakeDelegate(static function () {
292
                return new Response();
293
            })))
294
            ->getValue();
295
296
        self::assertIsString($tokenString);
297
298
        $token = (new Parser())->parse($tokenString);
299
300
        self::assertEquals($time, $token->getClaim(SessionMiddleware::ISSUED_AT_CLAIM), 'Token was refreshed');
301
    }
302
303
    /**
304
     * @dataProvider validMiddlewaresProvider
305
     */
306
    public function testWillSkipInjectingSessionCookiesWhenSessionIsNotChanged(SessionMiddleware $middleware) : void
307
    {
308
        $this->ensureSameResponse(
309
            $middleware,
310
            $this->requestWithResponseCookies(
311
                $middleware->process(new ServerRequest(), $this->writingMiddleware())
312
            ),
313
            $this->fakeDelegate(
314
                static function (ServerRequestInterface $request) {
315
                    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
316
                    assert($session instanceof SessionInterface);
317
318
                    // note: we set the same data just to make sure that we are indeed interacting with the session
319
                    $session->set('foo', 'bar');
320
321
                    self::assertFalse($session->hasChanged());
322
323
                    return new Response();
324
                }
325
            )
326
        );
327
    }
328
329
    /**
330
     * @dataProvider validMiddlewaresProvider
331
     */
332
    public function testWillSendExpirationCookieWhenSessionContentsAreCleared(SessionMiddleware $middleware) : void
333
    {
334
        $this->ensureClearsSessionCookie(
335
            $middleware,
336
            $this->requestWithResponseCookies(
337
                $middleware->process(new ServerRequest(), $this->writingMiddleware())
338
            ),
339
            $this->fakeDelegate(
340
                static function (ServerRequestInterface $request) {
341
                    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
342
                    assert($session instanceof SessionInterface);
343
344
                    $session->clear();
345
346
                    return new Response();
347
                }
348
            )
349
        );
350
    }
351
352
    /**
353
     * @dataProvider validMiddlewaresProvider
354
     */
355
    public function testWillIgnoreMalformedTokens(SessionMiddleware $middleware) : void
356
    {
357
        $this->ensureSameResponse(
358
            $middleware,
359
            (new ServerRequest())->withCookieParams([SessionMiddleware::DEFAULT_COOKIE => 'malformed content']),
360
            $this->emptyValidationMiddleware()
361
        );
362
    }
363
364
    public function testRejectsTokensWithInvalidSignature() : void
365
    {
366
        $middleware = new SessionMiddleware(
367
            new Sha256(),
368
            'foo',
369
            'bar', // wrong symmetric key (on purpose)
370
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
371
            new Parser(),
372
            100,
373
            new SystemClock()
374
        );
375
376
        $this->ensureSameResponse(
377
            $middleware,
378
            $this->requestWithResponseCookies(
379
                $middleware->process(new ServerRequest(), $this->writingMiddleware())
380
            ),
381
            $this->emptyValidationMiddleware()
382
        );
383
    }
384
385
    public function testAllowsModifyingCookieDetails() : void
386
    {
387
        $defaultCookie = SetCookie::create('a-different-cookie-name')
388
            ->withDomain('foo.bar')
389
            ->withPath('/yadda')
390
            ->withHttpOnly(false)
391
            ->withMaxAge(123123)
392
            ->withValue('a-random-value');
393
394
        $dateTime   = new DateTimeImmutable();
395
        $middleware = new SessionMiddleware(
396
            new Sha256(),
397
            'foo',
398
            'foo',
399
            $defaultCookie,
400
            new Parser(),
401
            123456,
402
            new FrozenClock($dateTime),
403
            123
404
        );
405
406
        $response = $middleware->process(new ServerRequest(), $this->writingMiddleware());
407
408
        self::assertNull($this->getCookie($response)->getValue());
409
410
        $tokenCookie = $this->getCookie($response, 'a-different-cookie-name');
411
412
        self::assertNotEmpty($tokenCookie->getValue());
413
        self::assertNotSame($defaultCookie->getValue(), $tokenCookie->getValue());
414
        self::assertSame($defaultCookie->getDomain(), $tokenCookie->getDomain());
415
        self::assertSame($defaultCookie->getPath(), $tokenCookie->getPath());
416
        self::assertSame($defaultCookie->getHttpOnly(), $tokenCookie->getHttpOnly());
417
        self::assertSame($defaultCookie->getMaxAge(), $tokenCookie->getMaxAge());
418
        self::assertEquals($dateTime->getTimestamp() + 123456, $tokenCookie->getExpires());
419
    }
420
421
    public function testSessionTokenParsingIsDelayedWhenSessionIsNotBeingUsed() : void
422
    {
423
        $signer = $this->createMock(Signer::class);
424
425
        $signer->expects(self::never())->method('verify');
426
        $signer->method('getAlgorithmId')->willReturn('HS256');
0 ignored issues
show
Bug introduced by
The method method() does not seem to exist on object<PHPUnit\Framework\MockObject\MockObject>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
427
428
        $currentTimeProvider = new SystemClock();
429
        $setCookie           = SetCookie::create(SessionMiddleware::DEFAULT_COOKIE);
430
        $middleware          = new SessionMiddleware($signer, 'foo', 'foo', $setCookie, new Parser(), 100, $currentTimeProvider);
431
        $request             = (new ServerRequest())
432
            ->withCookieParams([
433
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::set() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setIssuedAt() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::sign() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
434
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
435
                    ->setIssuedAt(time())
436
                    ->sign(new Sha256(), 'foo')
437
                    ->getToken(),
438
            ]);
439
440
        $middleware->process(
441
            $request,
442
            $this->fakeDelegate(static function (ServerRequestInterface $request) {
443
                self::assertInstanceOf(
444
                    SessionInterface::class,
445
                    $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE)
446
                );
447
448
                return new Response();
449
            })
450
        );
451
    }
452
453
    public function testShouldRegenerateTokenWhenRequestHasATokenThatIsAboutToExpire() : void
454
    {
455
        $dateTime   = new DateTimeImmutable();
456
        $middleware = new SessionMiddleware(
457
            new Sha256(),
458
            'foo',
459
            'foo',
460
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
461
            new Parser(),
462
            1000,
463
            new FrozenClock($dateTime),
464
            300
465
        );
466
467
        $expiringToken = (new ServerRequest())
468
            ->withCookieParams([
469
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setIssuedAt() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setExpiration() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::set() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::sign() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
470
                    ->setIssuedAt((new DateTime('-800 second'))->getTimestamp())
471
                    ->setExpiration((new DateTime('+200 second'))->getTimestamp())
472
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
473
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
474
                    ->getToken(),
475
            ]);
476
477
        $initialResponse = new Response();
478
479
        $response = $middleware->process($expiringToken, $this->fakeDelegate(static function () use ($initialResponse) {
480
            return $initialResponse;
481
        }));
482
483
        self::assertNotSame($initialResponse, $response);
484
485
        $tokenCookie = $this->getCookie($response);
486
487
        self::assertNotEmpty($tokenCookie->getValue());
488
        self::assertEquals($dateTime->getTimestamp() + 1000, $tokenCookie->getExpires());
489
    }
490
491
    public function testShouldNotRegenerateTokenWhenRequestHasATokenThatIsFarFromExpiration() : void
492
    {
493
        $middleware = new SessionMiddleware(
494
            new Sha256(),
495
            'foo',
496
            'foo',
497
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
498
            new Parser(),
499
            1000,
500
            new SystemClock(),
501
            300
502
        );
503
504
        $validToken = (new ServerRequest())
505
            ->withCookieParams([
506
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setIssuedAt() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setExpiration() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::set() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::sign() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
507
                    ->setIssuedAt((new DateTime('-100 second'))->getTimestamp())
508
                    ->setExpiration((new DateTime('+900 second'))->getTimestamp())
509
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
510
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
511
                    ->getToken(),
512
            ]);
513
514
        $this->ensureSameResponse($middleware, $validToken);
515
    }
516
517
    /**
518
     * @return SessionMiddleware[][]
519
     */
520
    public function validMiddlewaresProvider() : array
521
    {
522
        return [
523
            [new SessionMiddleware(
524
                new Sha256(),
525
                'foo',
526
                'foo',
527
                SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
528
                new Parser(),
529
                100,
530
                new SystemClock()
531
            ),
532
            ],
533
            [SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)],
534
            [SessionMiddleware::fromAsymmetricKeyDefaults(
535
                self::privateKey(),
536
                self::publicKey(),
537
                200
538
            ),
539
            ],
540
        ];
541
    }
542
543
    /**
544
     * @group #46
545
     */
546
    public function testFromSymmetricKeyDefaultsWillHaveADefaultSessionPath() : void
547
    {
548
        self::assertSame(
549
            '/',
550
            $this
551
                ->getCookie(
552
                    SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)
553
                        ->process(new ServerRequest(), $this->writingMiddleware())
554
                )
555
                ->getPath()
556
        );
557
    }
558
559
    public function testFromSymmetricKeyDefaultsWillHaveALaxSameSitePolicy() : void
560
    {
561
        self::assertEquals(
562
            SameSite::lax(),
563
            $this
564
                ->getCookie(
565
                    SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)
566
                        ->process(new ServerRequest(), $this->writingMiddleware())
567
                )
568
                ->getSameSite()
569
        );
570
    }
571
572
    /**
573
     * @throws InvalidArgumentException
574
     * @throws OutOfBoundsException
575
     *
576
     * @group #46
577
     */
578
    public function testFromAsymmetricKeyDefaultsWillHaveADefaultSessionPath() : void
579
    {
580
        self::assertSame(
581
            '/',
582
            $this
583
                ->getCookie(
584
                    SessionMiddleware::fromAsymmetricKeyDefaults(
585
                        self::privateKey(),
586
                        self::publicKey(),
587
                        200
588
                    )
589
                        ->process(new ServerRequest(), $this->writingMiddleware())
590
                )
591
                ->getPath()
592
        );
593
    }
594
595
    public function testFromAsymmetricKeyDefaultsWillHaveALaxSameSitePolicy() : void
596
    {
597
        self::assertEquals(
598
            SameSite::lax(),
599
            $this
600
                ->getCookie(
601
                    SessionMiddleware::fromAsymmetricKeyDefaults(
602
                        self::privateKey(),
603
                        self::publicKey(),
604
                        200
605
                    )
606
                        ->process(new ServerRequest(), $this->writingMiddleware())
607
                )
608
                ->getSameSite()
609
        );
610
    }
611
612
    public function testMutableCookieWillNotBeUsed() : void
613
    {
614
        $cookie = MutableBadCookie::create(SessionMiddleware::DEFAULT_COOKIE);
615
616
        assert($cookie instanceof MutableBadCookie);
0 ignored issues
show
Bug introduced by
The class PSR7SessionsTest\Storage...\Asset\MutableBadCookie does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
617
618
        $middleware = new SessionMiddleware(
619
            new Sha256(),
620
            'foo',
621
            'foo',
622
            $cookie,
623
            new Parser(),
624
            1000,
625
            new SystemClock()
626
        );
627
628
        $cookie->mutated = true;
629
630
        self::assertStringStartsWith(
631
            'slsession=',
632
            $middleware
633
                ->process(new ServerRequest(), $this->writingMiddleware())
634
                ->getHeaderLine('Set-Cookie')
635
        );
636
    }
637
638
    private function ensureSameResponse(
639
        SessionMiddleware $middleware,
640
        ServerRequestInterface $request,
641
        ?RequestHandlerInterface $next = null
642
    ) : ResponseInterface {
643
        $initialResponse = new Response();
644
645
        $handleRequest = $this->createMock(RequestHandlerInterface::class);
646
647
        if ($next === null) {
648
            $handleRequest
649
                ->expects(self::once())
650
                ->method('handle')
651
                ->willReturn($initialResponse);
652
        } else {
653
            // capturing `$initialResponse` from the `$next` handler
654
            $handleRequest
655
                ->expects(self::once())
656
                ->method('handle')
657
                ->willReturnCallback(static function (ServerRequestInterface $serverRequest) use ($next, & $initialResponse) {
658
                    $response = $next->handle($serverRequest);
659
660
                    $initialResponse = $response;
661
662
                    return $response;
663
                });
664
        }
665
666
        $response = $middleware->process($request, $handleRequest);
667
668
        self::assertSame($initialResponse, $response);
669
670
        return $response;
671
    }
672
673
    private function ensureClearsSessionCookie(
674
        SessionMiddleware $middleware,
675
        ServerRequestInterface $request,
676
        RequestHandlerInterface $next
677
    ) : ResponseInterface {
678
        $response = $middleware->process($request, $next);
679
680
        $cookie = $this->getCookie($response);
681
682
        self::assertLessThan((new DateTime('-29 day'))->getTimestamp(), $cookie->getExpires());
683
        self::assertEmpty($cookie->getValue());
684
685
        return $response;
686
    }
687
688
    private function createToken(SessionMiddleware $middleware, DateTime $issuedAt, DateTime $expiration) : string
689
    {
690
        return (string) (new Builder())
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setIssuedAt() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setExpiration() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::set() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::sign() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
691
            ->setIssuedAt($issuedAt->getTimestamp())
692
            ->setExpiration($expiration->getTimestamp())
693
            ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
694
            ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
695
            ->getToken();
696
    }
697
698
    /** @param mixed $claim */
699
    private function createTokenWithCustomClaim(
700
        SessionMiddleware $middleware,
701
        DateTime $issuedAt,
702
        DateTime $expiration,
703
        $claim
704
    ) : string {
705
        return (string) (new Builder())
0 ignored issues
show
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setIssuedAt() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::setExpiration() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::set() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
Deprecated Code introduced by
The method Lcobucci\JWT\Builder::sign() has been deprecated with message: This method will be removed on v4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
706
            ->setIssuedAt($issuedAt->getTimestamp())
707
            ->setExpiration($expiration->getTimestamp())
708
            ->set(SessionMiddleware::SESSION_CLAIM, $claim)
709
            ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
710
            ->getToken();
711
    }
712
713
    private function emptyValidationMiddleware() : RequestHandlerInterface
714
    {
715
        return $this->fakeDelegate(
716
            static function (ServerRequestInterface $request) {
717
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
718
719
                self::assertInstanceOf(SessionInterface::class, $session);
720
                self::assertTrue($session->isEmpty());
721
722
                return new Response();
723
            }
724
        );
725
    }
726
727
    private function writingMiddleware(string $value = 'bar') : RequestHandlerInterface
728
    {
729
        return $this->fakeDelegate(
730
            static function (ServerRequestInterface $request) use ($value) {
731
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
732
                assert($session instanceof SessionInterface);
733
                $session->set('foo', $value);
734
735
                return new Response();
736
            }
737
        );
738
    }
739
740
    private function fakeDelegate(callable $callback) : RequestHandlerInterface
741
    {
742
        $middleware = $this->createMock(RequestHandlerInterface::class);
743
744
        $middleware
745
            ->expects(self::once())
746
           ->method('handle')
747
           ->willReturnCallback($callback)
748
           ->with(self::isInstanceOf(RequestInterface::class));
749
750
        return $middleware;
751
    }
752
753
    /**
754
     * @return ServerRequest
755
     */
756
    private function requestWithResponseCookies(ResponseInterface $response) : ServerRequestInterface
757
    {
758
        return (new ServerRequest())->withCookieParams([
759
            SessionMiddleware::DEFAULT_COOKIE => $this->getCookie($response)->getValue(),
760
        ]);
761
    }
762
763
    private function getCookie(ResponseInterface $response, string $name = SessionMiddleware::DEFAULT_COOKIE) : SetCookie
764
    {
765
        return FigResponseCookies::get($response, $name);
766
    }
767
768
    private function getSigner(SessionMiddleware $middleware) : Signer
769
    {
770
        $property = new ReflectionProperty(SessionMiddleware::class, 'signer');
771
772
        $property->setAccessible(true);
773
774
        return $property->getValue($middleware);
775
    }
776
777
    private function getSignatureKey(SessionMiddleware $middleware) : string
778
    {
779
        $property = new ReflectionProperty(SessionMiddleware::class, 'signatureKey');
780
781
        $property->setAccessible(true);
782
783
        return $property->getValue($middleware);
784
    }
785
786
    private static function privateKey() : string
787
    {
788
        $key = file_get_contents(__DIR__ . '/../../keys/private_key.pem');
789
790
        self::assertIsString($key);
791
792
        return $key;
793
    }
794
795
    private static function publicKey() : string
796
    {
797
        $key = file_get_contents(__DIR__ . '/../../keys/public_key.pem');
798
799
        self::assertIsString($key);
800
801
        return $key;
802
    }
803
}
804