Passed
Pull Request — master (#119)
by Filippo
02:38
created

testDefaultMiddlewareConfiguresASecureCookie()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 1
nc 1
nop 1
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 Laminas\Diactoros\Response;
29
use Laminas\Diactoros\ServerRequest;
30
use Lcobucci\Clock\FrozenClock;
31
use Lcobucci\Clock\SystemClock;
32
use Lcobucci\JWT\Builder;
33
use Lcobucci\JWT\Parser;
34
use Lcobucci\JWT\Signer;
35
use Lcobucci\JWT\Signer\Hmac\Sha256;
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 function assert;
47
use function file_get_contents;
48
use function random_int;
49
use function time;
50
use function uniqid;
51
52
final class SessionMiddlewareTest extends TestCase
53
{
54
    /**
55
     * @see https://tools.ietf.org/html/rfc6265#section-4.1.2.5 for Secure flag
56
     * @see https://tools.ietf.org/html/rfc6265#section-4.1.2.6 for HttpOnly flag
57
     * @see https://github.com/psr7-sessions/storageless/pull/46 for / path
58
     * @see https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site for SameSite flag
59
     * @see https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes for __Secure- prefix
60
     *
61
     * @dataProvider defaultMiddlewaresProvider
62
     * @group #46
63
     */
64
    public function testDefaultMiddlewareConfiguresASecureCookie(SessionMiddleware $middleware) : void
65
    {
66
        $response = $middleware->process(new ServerRequest(), $this->writingMiddleware());
67
68
        $cookie = $this->getCookie($response);
69
70
        self::assertTrue($cookie->getSecure());
71
        self::assertTrue($cookie->getHttpOnly());
72
        self::assertSame('/', $cookie->getPath());
73
        self::assertEquals(SameSite::lax(), $cookie->getSameSite());
74
        self::assertStringStartsWith('__Secure-', $cookie->getName());
75
    }
76
77
    /**
78
     * @dataProvider validMiddlewaresProvider
79
     */
80
    public function testSkipsInjectingSessionCookieOnEmptyContainer(SessionMiddleware $middleware) : void
81
    {
82
        $response = $this->ensureSameResponse($middleware, new ServerRequest(), $this->emptyValidationMiddleware());
83
84
        self::assertNull($this->getCookie($response)->getValue());
85
    }
86
87
    /**
88
     * @dataProvider validMiddlewaresProvider
89
     */
90
    public function testExtractsSessionContainerFromEmptyRequest(SessionMiddleware $middleware) : void
91
    {
92
        $this->ensureSameResponse($middleware, new ServerRequest(), $this->emptyValidationMiddleware());
93
    }
94
95
    /**
96
     * @dataProvider validMiddlewaresProvider
97
     */
98
    public function testInjectsSessionInResponseCookies(SessionMiddleware $middleware) : void
99
    {
100
        $initialResponse = new Response();
101
        $response        = $middleware->process(new ServerRequest(), $this->writingMiddleware());
102
103
        self::assertNotSame($initialResponse, $response);
104
        self::assertEmpty($this->getCookie($response, 'non-existing')->getValue());
105
106
        $token = $this->getCookie($response)->getValue();
107
108
        self::assertIsString($token);
109
        self::assertEquals((object) ['foo' => 'bar'], (new Parser())->parse($token)->getClaim('session-data'));
110
    }
111
112
    /**
113
     * @dataProvider validMiddlewaresProvider
114
     */
115
    public function testSessionContainerCanBeReusedOverMultipleRequests(SessionMiddleware $middleware) : void
116
    {
117
        $sessionValue = uniqid('', true);
118
119
        $checkingMiddleware = $this->fakeDelegate(
120
            static function (ServerRequestInterface $request) use ($sessionValue) {
121
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
122
                assert($session instanceof SessionInterface);
123
124
                self::assertSame($sessionValue, $session->get('foo'));
125
                self::assertFalse($session->hasChanged());
126
127
                $session->set('foo', $sessionValue . 'changed');
128
129
                self::assertTrue(
130
                    $session->hasChanged(),
131
                    'ensuring that the cookie is sent again: '
132
                    . 'non-modified session containers are not to be re-serialized into a token'
133
                );
134
135
                return new Response();
136
            }
137
        );
138
139
        $firstResponse = $middleware->process(new ServerRequest(), $this->writingMiddleware($sessionValue));
140
141
        $response = $middleware->process(
142
            $this->requestWithResponseCookies($firstResponse),
143
            $checkingMiddleware
144
        );
145
146
        self::assertNotSame($response, $firstResponse);
147
    }
148
149
    /**
150
     * @dataProvider validMiddlewaresProvider
151
     */
152
    public function testSessionContainerCanBeCreatedEvenIfTokenDataIsMalformed(SessionMiddleware $middleware) : void
153
    {
154
        $sessionValue = uniqid('not valid session data', true);
155
156
        $checkingMiddleware = $this->fakeDelegate(
157
            static function (ServerRequestInterface $request) use ($sessionValue) {
158
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
159
                assert($session instanceof SessionInterface);
160
161
                self::assertSame($sessionValue, $session->get('scalar'));
162
                self::assertFalse($session->hasChanged());
163
164
                return new Response();
165
            }
166
        );
167
168
        $this->createTokenWithCustomClaim(
169
            $middleware,
170
            new DateTime('-1 day'),
171
            new DateTime('+1 day'),
172
            'not valid session data'
173
        );
174
175
        $middleware->process(
176
            (new ServerRequest())
177
                ->withCookieParams([
178
                    SessionMiddleware::DEFAULT_COOKIE => $this->createTokenWithCustomClaim(
179
                        $middleware,
180
                        new DateTime('-1 day'),
181
                        new DateTime('+1 day'),
182
                        $sessionValue
183
                    ),
184
                ]),
185
            $checkingMiddleware
186
        );
187
    }
188
189
    /**
190
     * @dataProvider validMiddlewaresProvider
191
     */
192
    public function testWillIgnoreRequestsWithExpiredTokens(SessionMiddleware $middleware) : void
193
    {
194
        $expiredToken = (new ServerRequest())
195
            ->withCookieParams([
196
                SessionMiddleware::DEFAULT_COOKIE => $this->createToken(
197
                    $middleware,
198
                    new DateTime('-1 day'),
199
                    new DateTime('-2 day')
200
                ),
201
            ]);
202
203
        $this->ensureSameResponse($middleware, $expiredToken, $this->emptyValidationMiddleware());
204
    }
205
206
    /**
207
     * @dataProvider validMiddlewaresProvider
208
     */
209
    public function testWillIgnoreRequestsWithTokensFromFuture(SessionMiddleware $middleware) : void
210
    {
211
        $tokenInFuture = (new ServerRequest())
212
            ->withCookieParams([
213
                SessionMiddleware::DEFAULT_COOKIE => $this->createToken(
214
                    $middleware,
215
                    new DateTime('+1 day'),
216
                    new DateTime('-2 day')
217
                ),
218
            ]);
219
220
        $this->ensureSameResponse($middleware, $tokenInFuture, $this->emptyValidationMiddleware());
221
    }
222
223
    /**
224
     * @dataProvider validMiddlewaresProvider
225
     */
226
    public function testWillIgnoreUnSignedTokens(SessionMiddleware $middleware) : void
227
    {
228
        $unsignedToken = (new ServerRequest())
229
            ->withCookieParams([
230
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
231
                    ->setIssuedAt((new DateTime('-1 day'))->getTimestamp())
232
                    ->setExpiration((new DateTime('+1 day'))->getTimestamp())
233
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
234
                    ->getToken(),
235
            ]);
236
237
        $this->ensureSameResponse($middleware, $unsignedToken, $this->emptyValidationMiddleware());
238
    }
239
240
    /**
241
     * @dataProvider validMiddlewaresProvider
242
     */
243
    public function testWillNotRefreshSignedTokensWithoutIssuedAt(SessionMiddleware $middleware) : void
244
    {
245
        $unsignedToken = (new ServerRequest())
246
            ->withCookieParams([
247
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
248
                    ->setExpiration((new DateTime('+1 day'))->getTimestamp())
249
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
250
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
251
                    ->getToken(),
252
            ]);
253
254
        $this->ensureSameResponse($middleware, $unsignedToken);
255
    }
256
257
    public function testWillRefreshTokenWithIssuedAtExactlyAtTokenRefreshTimeThreshold() : void
258
    {
259
        // forcing ourselves to think of time as a mutable value:
260
        $time = time() + random_int(-100, +100);
261
262
        $clock = new FrozenClock(new DateTimeImmutable('@' . $time));
263
264
        $middleware = new SessionMiddleware(
265
            new Sha256(),
266
            'foo',
267
            'foo',
268
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
269
            new Parser(),
270
            1000,
271
            $clock,
272
            100
273
        );
274
275
        $requestWithTokenIssuedInThePast = (new ServerRequest())
276
            ->withCookieParams([
277
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
278
                    ->setExpiration($time + 10000)
279
                    ->setIssuedAt($time - 100)
280
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
281
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
282
                    ->getToken(),
283
            ]);
284
285
        $tokenString = $this
286
            ->getCookie($middleware->process($requestWithTokenIssuedInThePast, $this->fakeDelegate(static function () {
287
                return new Response();
288
            })))
289
            ->getValue();
290
291
        self::assertIsString($tokenString);
292
293
        $token = (new Parser())->parse($tokenString);
294
295
        self::assertEquals($time, $token->getClaim(SessionMiddleware::ISSUED_AT_CLAIM), 'Token was refreshed');
296
    }
297
298
    /**
299
     * @dataProvider validMiddlewaresProvider
300
     */
301
    public function testWillSkipInjectingSessionCookiesWhenSessionIsNotChanged(SessionMiddleware $middleware) : void
302
    {
303
        $this->ensureSameResponse(
304
            $middleware,
305
            $this->requestWithResponseCookies(
306
                $middleware->process(new ServerRequest(), $this->writingMiddleware())
307
            ),
308
            $this->fakeDelegate(
309
                static function (ServerRequestInterface $request) {
310
                    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
311
                    assert($session instanceof SessionInterface);
312
313
                    // note: we set the same data just to make sure that we are indeed interacting with the session
314
                    $session->set('foo', 'bar');
315
316
                    self::assertFalse($session->hasChanged());
317
318
                    return new Response();
319
                }
320
            )
321
        );
322
    }
323
324
    /**
325
     * @dataProvider validMiddlewaresProvider
326
     */
327
    public function testWillSendExpirationCookieWhenSessionContentsAreCleared(SessionMiddleware $middleware) : void
328
    {
329
        $this->ensureClearsSessionCookie(
330
            $middleware,
331
            $this->requestWithResponseCookies(
332
                $middleware->process(new ServerRequest(), $this->writingMiddleware())
333
            ),
334
            $this->fakeDelegate(
335
                static function (ServerRequestInterface $request) {
336
                    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
337
                    assert($session instanceof SessionInterface);
338
339
                    $session->clear();
340
341
                    return new Response();
342
                }
343
            )
344
        );
345
    }
346
347
    /**
348
     * @dataProvider validMiddlewaresProvider
349
     */
350
    public function testWillIgnoreMalformedTokens(SessionMiddleware $middleware) : void
351
    {
352
        $this->ensureSameResponse(
353
            $middleware,
354
            (new ServerRequest())->withCookieParams([SessionMiddleware::DEFAULT_COOKIE => 'malformed content']),
355
            $this->emptyValidationMiddleware()
356
        );
357
    }
358
359
    public function testRejectsTokensWithInvalidSignature() : void
360
    {
361
        $middleware = new SessionMiddleware(
362
            new Sha256(),
363
            'foo',
364
            'bar', // wrong symmetric key (on purpose)
365
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
366
            new Parser(),
367
            100,
368
            new SystemClock()
369
        );
370
371
        $this->ensureSameResponse(
372
            $middleware,
373
            $this->requestWithResponseCookies(
374
                $middleware->process(new ServerRequest(), $this->writingMiddleware())
375
            ),
376
            $this->emptyValidationMiddleware()
377
        );
378
    }
379
380
    public function testAllowsModifyingCookieDetails() : void
381
    {
382
        $defaultCookie = SetCookie::create('a-different-cookie-name')
383
            ->withDomain('foo.bar')
384
            ->withPath('/yadda')
385
            ->withHttpOnly(false)
386
            ->withMaxAge(123123)
387
            ->withValue('a-random-value');
388
389
        $dateTime   = new DateTimeImmutable();
390
        $middleware = new SessionMiddleware(
391
            new Sha256(),
392
            'foo',
393
            'foo',
394
            $defaultCookie,
395
            new Parser(),
396
            123456,
397
            new FrozenClock($dateTime),
398
            123
399
        );
400
401
        $response = $middleware->process(new ServerRequest(), $this->writingMiddleware());
402
403
        self::assertNull($this->getCookie($response)->getValue());
404
405
        $tokenCookie = $this->getCookie($response, 'a-different-cookie-name');
406
407
        self::assertNotEmpty($tokenCookie->getValue());
408
        self::assertNotSame($defaultCookie->getValue(), $tokenCookie->getValue());
409
        self::assertSame($defaultCookie->getDomain(), $tokenCookie->getDomain());
410
        self::assertSame($defaultCookie->getPath(), $tokenCookie->getPath());
411
        self::assertSame($defaultCookie->getHttpOnly(), $tokenCookie->getHttpOnly());
412
        self::assertSame($defaultCookie->getMaxAge(), $tokenCookie->getMaxAge());
413
        self::assertEquals($dateTime->getTimestamp() + 123456, $tokenCookie->getExpires());
414
    }
415
416
    public function testSessionTokenParsingIsDelayedWhenSessionIsNotBeingUsed() : void
417
    {
418
        $signer = $this->createMock(Signer::class);
419
420
        $signer->expects(self::never())->method('verify');
421
        $signer->method('getAlgorithmId')->willReturn('HS256');
422
423
        $currentTimeProvider = new SystemClock();
424
        $setCookie           = SetCookie::create(SessionMiddleware::DEFAULT_COOKIE);
425
        $middleware          = new SessionMiddleware($signer, 'foo', 'foo', $setCookie, new Parser(), 100, $currentTimeProvider);
426
        $request             = (new ServerRequest())
427
            ->withCookieParams([
428
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
429
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
430
                    ->setIssuedAt(time())
431
                    ->sign(new Sha256(), 'foo')
432
                    ->getToken(),
433
            ]);
434
435
        $middleware->process(
436
            $request,
437
            $this->fakeDelegate(static function (ServerRequestInterface $request) {
438
                self::assertInstanceOf(
439
                    SessionInterface::class,
440
                    $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE)
441
                );
442
443
                return new Response();
444
            })
445
        );
446
    }
447
448
    public function testShouldRegenerateTokenWhenRequestHasATokenThatIsAboutToExpire() : void
449
    {
450
        $dateTime   = new DateTimeImmutable();
451
        $middleware = new SessionMiddleware(
452
            new Sha256(),
453
            'foo',
454
            'foo',
455
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
456
            new Parser(),
457
            1000,
458
            new FrozenClock($dateTime),
459
            300
460
        );
461
462
        $expiringToken = (new ServerRequest())
463
            ->withCookieParams([
464
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
465
                    ->setIssuedAt((new DateTime('-800 second'))->getTimestamp())
466
                    ->setExpiration((new DateTime('+200 second'))->getTimestamp())
467
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
468
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
469
                    ->getToken(),
470
            ]);
471
472
        $initialResponse = new Response();
473
474
        $response = $middleware->process($expiringToken, $this->fakeDelegate(static function () use ($initialResponse) {
475
            return $initialResponse;
476
        }));
477
478
        self::assertNotSame($initialResponse, $response);
479
480
        $tokenCookie = $this->getCookie($response);
481
482
        self::assertNotEmpty($tokenCookie->getValue());
483
        self::assertEquals($dateTime->getTimestamp() + 1000, $tokenCookie->getExpires());
484
    }
485
486
    public function testShouldNotRegenerateTokenWhenRequestHasATokenThatIsFarFromExpiration() : void
487
    {
488
        $middleware = new SessionMiddleware(
489
            new Sha256(),
490
            'foo',
491
            'foo',
492
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
493
            new Parser(),
494
            1000,
495
            new SystemClock(),
496
            300
497
        );
498
499
        $validToken = (new ServerRequest())
500
            ->withCookieParams([
501
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
502
                    ->setIssuedAt((new DateTime('-100 second'))->getTimestamp())
503
                    ->setExpiration((new DateTime('+900 second'))->getTimestamp())
504
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
505
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
506
                    ->getToken(),
507
            ]);
508
509
        $this->ensureSameResponse($middleware, $validToken);
510
    }
511
512
    /**
513
     * @return SessionMiddleware[][]
514
     */
515
    public function validMiddlewaresProvider() : array
516
    {
517
        return $this->defaultMiddlewaresProvider() + [
518
            [new SessionMiddleware(
519
                new Sha256(),
520
                'foo',
521
                'foo',
522
                SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
523
                new Parser(),
524
                100,
525
                new SystemClock()
526
            ),
527
            ],
528
        ];
529
    }
530
531
    /**
532
     * @return SessionMiddleware[][]
533
     */
534
    public function defaultMiddlewaresProvider() : array
535
    {
536
        return [
537
            [SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)],
538
            [SessionMiddleware::fromAsymmetricKeyDefaults(
539
                self::privateKey(),
540
                self::publicKey(),
541
                200
542
            ),
543
            ],
544
        ];
545
    }
546
547
    public function testMutableCookieWillNotBeUsed() : void
548
    {
549
        $cookie = MutableBadCookie::create(SessionMiddleware::DEFAULT_COOKIE);
550
551
        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...
552
553
        $middleware = new SessionMiddleware(
554
            new Sha256(),
555
            'foo',
556
            'foo',
557
            $cookie,
558
            new Parser(),
559
            1000,
560
            new SystemClock()
561
        );
562
563
        $cookie->mutated = true;
564
565
        self::assertStringStartsWith(
566
            '__Secure-slsession=',
567
            $middleware
568
                ->process(new ServerRequest(), $this->writingMiddleware())
569
                ->getHeaderLine('Set-Cookie')
570
        );
571
    }
572
573
    private function ensureSameResponse(
574
        SessionMiddleware $middleware,
575
        ServerRequestInterface $request,
576
        ?RequestHandlerInterface $next = null
577
    ) : ResponseInterface {
578
        $initialResponse = new Response();
579
580
        $handleRequest = $this->createMock(RequestHandlerInterface::class);
581
582
        if ($next === null) {
583
            $handleRequest
584
                ->expects(self::once())
585
                ->method('handle')
586
                ->willReturn($initialResponse);
587
        } else {
588
            // capturing `$initialResponse` from the `$next` handler
589
            $handleRequest
590
                ->expects(self::once())
591
                ->method('handle')
592
                ->willReturnCallback(static function (ServerRequestInterface $serverRequest) use ($next, & $initialResponse) {
593
                    $response = $next->handle($serverRequest);
594
595
                    $initialResponse = $response;
596
597
                    return $response;
598
                });
599
        }
600
601
        $response = $middleware->process($request, $handleRequest);
602
603
        self::assertSame($initialResponse, $response);
604
605
        return $response;
606
    }
607
608
    private function ensureClearsSessionCookie(
609
        SessionMiddleware $middleware,
610
        ServerRequestInterface $request,
611
        RequestHandlerInterface $next
612
    ) : ResponseInterface {
613
        $response = $middleware->process($request, $next);
614
615
        $cookie = $this->getCookie($response);
616
617
        self::assertLessThan((new DateTime('-29 day'))->getTimestamp(), $cookie->getExpires());
618
        self::assertEmpty($cookie->getValue());
619
620
        return $response;
621
    }
622
623
    private function createToken(SessionMiddleware $middleware, DateTime $issuedAt, DateTime $expiration) : string
624
    {
625
        return (string) (new Builder())
626
            ->setIssuedAt($issuedAt->getTimestamp())
627
            ->setExpiration($expiration->getTimestamp())
628
            ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
629
            ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
630
            ->getToken();
631
    }
632
633
    /** @param mixed $claim */
634
    private function createTokenWithCustomClaim(
635
        SessionMiddleware $middleware,
636
        DateTime $issuedAt,
637
        DateTime $expiration,
638
        $claim
639
    ) : string {
640
        return (string) (new Builder())
641
            ->setIssuedAt($issuedAt->getTimestamp())
642
            ->setExpiration($expiration->getTimestamp())
643
            ->set(SessionMiddleware::SESSION_CLAIM, $claim)
644
            ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
645
            ->getToken();
646
    }
647
648
    private function emptyValidationMiddleware() : RequestHandlerInterface
649
    {
650
        return $this->fakeDelegate(
651
            static function (ServerRequestInterface $request) {
652
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
653
654
                self::assertInstanceOf(SessionInterface::class, $session);
655
                self::assertTrue($session->isEmpty());
656
657
                return new Response();
658
            }
659
        );
660
    }
661
662
    private function writingMiddleware(string $value = 'bar') : RequestHandlerInterface
663
    {
664
        return $this->fakeDelegate(
665
            static function (ServerRequestInterface $request) use ($value) {
666
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
667
                assert($session instanceof SessionInterface);
668
                $session->set('foo', $value);
669
670
                return new Response();
671
            }
672
        );
673
    }
674
675
    private function fakeDelegate(callable $callback) : RequestHandlerInterface
676
    {
677
        $middleware = $this->createMock(RequestHandlerInterface::class);
678
679
        $middleware
680
            ->expects(self::once())
681
           ->method('handle')
682
           ->willReturnCallback($callback)
683
           ->with(self::isInstanceOf(RequestInterface::class));
684
685
        return $middleware;
686
    }
687
688
    /**
689
     * @return ServerRequest
690
     */
691
    private function requestWithResponseCookies(ResponseInterface $response) : ServerRequestInterface
692
    {
693
        return (new ServerRequest())->withCookieParams([
694
            SessionMiddleware::DEFAULT_COOKIE => $this->getCookie($response)->getValue(),
695
        ]);
696
    }
697
698
    private function getCookie(ResponseInterface $response, string $name = SessionMiddleware::DEFAULT_COOKIE) : SetCookie
699
    {
700
        return FigResponseCookies::get($response, $name);
701
    }
702
703
    private function getSigner(SessionMiddleware $middleware) : Signer
704
    {
705
        $property = new ReflectionProperty(SessionMiddleware::class, 'signer');
706
707
        $property->setAccessible(true);
708
709
        return $property->getValue($middleware);
710
    }
711
712
    private function getSignatureKey(SessionMiddleware $middleware) : string
713
    {
714
        $property = new ReflectionProperty(SessionMiddleware::class, 'signatureKey');
715
716
        $property->setAccessible(true);
717
718
        return $property->getValue($middleware);
719
    }
720
721
    private static function privateKey() : string
722
    {
723
        $key = file_get_contents(__DIR__ . '/../../keys/private_key.pem');
724
725
        self::assertIsString($key);
726
727
        return $key;
728
    }
729
730
    private static function publicKey() : string
731
    {
732
        $key = file_get_contents(__DIR__ . '/../../keys/public_key.pem');
733
734
        self::assertIsString($key);
735
736
        return $key;
737
    }
738
}
739