Completed
Pull Request — master (#79)
by Marco
56:14 queued 21:13
created

SessionMiddlewareTest::getPropertyValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
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 DateTimeImmutable;
24
use Dflydev\FigCookies\FigResponseCookies;
25
use Dflydev\FigCookies\Modifier\SameSite;
26
use Dflydev\FigCookies\SetCookie;
27
use Lcobucci\Clock\FrozenClock;
28
use Lcobucci\Clock\SystemClock;
29
use Lcobucci\JWT\Builder;
30
use Lcobucci\JWT\Parser;
31
use Lcobucci\JWT\Signer;
32
use Lcobucci\JWT\Signer\Hmac\Sha256;
33
use PHPUnit\Framework\MockObject\MockObject;
34
use PHPUnit\Framework\TestCase;
35
use Psr\Http\Message\RequestInterface;
36
use Psr\Http\Message\ResponseInterface;
37
use Psr\Http\Message\ServerRequestInterface;
38
use Psr\Http\Server\RequestHandlerInterface;
39
use PSR7Sessions\Storageless\Http\SessionMiddleware;
40
use PSR7Sessions\Storageless\Session\DefaultSessionData;
41
use PSR7Sessions\Storageless\Session\SessionInterface;
42
use Zend\Diactoros\Response;
43
use Zend\Diactoros\ServerRequest;
44
use function file_get_contents;
45
use function random_int;
46
use function time;
47
use function uniqid;
48
49
final class SessionMiddlewareTest extends TestCase
50
{
51
    public function testFromSymmetricKeyDefaultsUsesASecureCookie() : void
52
    {
53
        $response = SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)
54
            ->process(new ServerRequest(), $this->writingMiddleware());
55
56
        $cookie = $this->getCookie($response);
57
58
        self::assertTrue($cookie->getSecure());
59
        self::assertTrue($cookie->getHttpOnly());
60
    }
61
62
    public function testFromAsymmetricKeyDefaultsUsesASecureCookie() : void
63
    {
64
        $response = SessionMiddleware::fromAsymmetricKeyDefaults(
65
            self::privateKey(),
66
            self::publicKey(),
67
            200
68
        )
69
            ->process(new ServerRequest(), $this->writingMiddleware());
70
71
        $cookie = $this->getCookie($response);
72
73
        self::assertTrue($cookie->getSecure());
74
        self::assertTrue($cookie->getHttpOnly());
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::assertInternalType('string', $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
            function (ServerRequestInterface $request) use ($sessionValue) {
121
                /** @var SessionInterface $session */
122
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
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
            function (ServerRequestInterface $request) use ($sessionValue) {
158
                /** @var SessionInterface $session */
159
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
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(function () {
287
                return new Response();
288
            })))
289
            ->getValue();
290
291
        self::assertInternalType('string', $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
                function (ServerRequestInterface $request) {
310
                    /** @var SessionInterface $session */
311
                    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
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
                function (ServerRequestInterface $request) {
336
                    /** @var SessionInterface $session */
337
                    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
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
        /** @var Signer|MockObject $signer */
419
        $signer = $this->createMock(Signer::class);
420
421
        $signer->expects(self::never())->method('verify');
422
        $signer->method('getAlgorithmId')->willReturn('HS256');
423
424
        $currentTimeProvider = new SystemClock();
425
        $setCookie           = SetCookie::create(SessionMiddleware::DEFAULT_COOKIE);
426
        $middleware          = new SessionMiddleware($signer, 'foo', 'foo', $setCookie, new Parser(), 100, $currentTimeProvider);
427
        $request             = (new ServerRequest())
428
            ->withCookieParams([
429
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
430
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
431
                    ->setIssuedAt(time())
432
                    ->sign(new Sha256(), 'foo')
433
                    ->getToken(),
434
            ]);
435
436
        $middleware->process(
437
            $request,
438
            $this->fakeDelegate(function (ServerRequestInterface $request) {
439
                self::assertInstanceOf(
440
                    SessionInterface::class,
441
                    $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE)
442
                );
443
444
                return new Response();
445
            })
446
        );
447
    }
448
449
    public function testShouldRegenerateTokenWhenRequestHasATokenThatIsAboutToExpire() : void
450
    {
451
        $dateTime   = new DateTimeImmutable();
452
        $middleware = new SessionMiddleware(
453
            new Sha256(),
454
            'foo',
455
            'foo',
456
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
457
            new Parser(),
458
            1000,
459
            new FrozenClock($dateTime),
460
            300
461
        );
462
463
        $expiringToken = (new ServerRequest())
464
            ->withCookieParams([
465
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
466
                    ->setIssuedAt((new \DateTime('-800 second'))->getTimestamp())
467
                    ->setExpiration((new \DateTime('+200 second'))->getTimestamp())
468
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
469
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
470
                    ->getToken(),
471
            ]);
472
473
        $initialResponse = new Response();
474
475
        $response = $middleware->process($expiringToken, $this->fakeDelegate(function () use ($initialResponse) {
476
            return $initialResponse;
477
        }));
478
479
        self::assertNotSame($initialResponse, $response);
480
481
        $tokenCookie = $this->getCookie($response);
482
483
        self::assertNotEmpty($tokenCookie->getValue());
484
        self::assertEquals($dateTime->getTimestamp() + 1000, $tokenCookie->getExpires());
485
    }
486
487
    public function testShouldNotRegenerateTokenWhenRequestHasATokenThatIsFarFromExpiration() : void
488
    {
489
        $middleware = new SessionMiddleware(
490
            new Sha256(),
491
            'foo',
492
            'foo',
493
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
494
            new Parser(),
495
            1000,
496
            new SystemClock(),
497
            300
498
        );
499
500
        $validToken = (new ServerRequest())
501
            ->withCookieParams([
502
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
503
                    ->setIssuedAt((new \DateTime('-100 second'))->getTimestamp())
504
                    ->setExpiration((new \DateTime('+900 second'))->getTimestamp())
505
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
506
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
507
                    ->getToken(),
508
            ]);
509
510
        $this->ensureSameResponse($middleware, $validToken);
511
    }
512
513
    /**
514
     * @return SessionMiddleware[][]
515
     */
516
    public function validMiddlewaresProvider() : array
517
    {
518
        return [
519
            [new SessionMiddleware(
520
                new Sha256(),
521
                'foo',
522
                'foo',
523
                SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
524
                new Parser(),
525
                100,
526
                new SystemClock()
527
            ),
528
            ],
529
            [SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)],
530
            [SessionMiddleware::fromAsymmetricKeyDefaults(
531
                self::privateKey(),
532
                self::publicKey(),
533
                200
534
            ),
535
            ],
536
        ];
537
    }
538
539
    /**
540
     * @group #46
541
     */
542
    public function testFromSymmetricKeyDefaultsWillHaveADefaultSessionPath() : void
543
    {
544
        self::assertSame(
545
            '/',
546
            $this
547
                ->getCookie(
548
                    SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)
549
                        ->process(new ServerRequest(), $this->writingMiddleware())
550
                )
551
                ->getPath()
552
        );
553
    }
554
555
    public function testFromSymmetricKeyDefaultsWillHaveALaxSameSitePolicy() : void
556
    {
557
        self::assertEquals(
558
            SameSite::lax(),
559
            $this
560
                ->getCookie(
561
                    SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)
562
                        ->process(new ServerRequest(), $this->writingMiddleware())
563
                )
564
                ->getSameSite()
565
        );
566
    }
567
568
    /**
569
     * @group #46
570
     *
571
     * @throws \InvalidArgumentException
572
     * @throws \OutOfBoundsException
573
     */
574
    public function testFromAsymmetricKeyDefaultsWillHaveADefaultSessionPath() : void
575
    {
576
        self::assertSame(
577
            '/',
578
            $this
579
                ->getCookie(
580
                    SessionMiddleware::fromAsymmetricKeyDefaults(
581
                        self::privateKey(),
582
                        self::publicKey(),
583
                        200
584
                    )
585
                        ->process(new ServerRequest(), $this->writingMiddleware())
586
                )
587
                ->getPath()
588
        );
589
    }
590
591
    public function testFromAsymmetricKeyDefaultsWillHaveALaxSameSitePolicy() : void
592
    {
593
        self::assertEquals(
594
            SameSite::lax(),
595
            $this
596
                ->getCookie(
597
                    SessionMiddleware::fromAsymmetricKeyDefaults(
598
                        self::privateKey(),
599
                        self::publicKey(),
600
                        200
601
                    )
602
                        ->process(new ServerRequest(), $this->writingMiddleware())
603
                )
604
                ->getSameSite()
605
        );
606
    }
607
608
    private function ensureSameResponse(
609
        SessionMiddleware $middleware,
610
        ServerRequestInterface $request,
611
        ?RequestHandlerInterface $next = null
612
    ) : ResponseInterface {
613
        $initialResponse = new Response();
614
615
        $handleRequest = $this->createMock(RequestHandlerInterface::class);
616
617
        if ($next === null) {
618
            $handleRequest
619
                ->expects(self::once())
620
                ->method('handle')
621
                ->willReturn($initialResponse);
622
        } else {
623
            // capturing `$initialResponse` from the `$next` handler
624
            $handleRequest
625
                ->expects(self::once())
626
                ->method('handle')
627
                ->willReturnCallback(function (ServerRequestInterface $serverRequest) use ($next, & $initialResponse) {
628
                    $initialResponse = $next->handle($serverRequest);
629
630
                    return $initialResponse;
631
                });
632
        }
633
634
        $response = $middleware->process($request, $handleRequest);
635
636
        self::assertSame($initialResponse, $response);
637
638
        return $response;
639
    }
640
641
    private function ensureClearsSessionCookie(
642
        SessionMiddleware $middleware,
643
        ServerRequestInterface $request,
644
        RequestHandlerInterface $next
645
    ) : ResponseInterface {
646
        $response = $middleware->process($request, $next);
647
648
        $cookie = $this->getCookie($response);
649
650
        self::assertLessThan((new \DateTime('-29 day'))->getTimestamp(), $cookie->getExpires());
651
        self::assertEmpty($cookie->getValue());
652
653
        return $response;
654
    }
655
656
    private function createToken(SessionMiddleware $middleware, \DateTime $issuedAt, \DateTime $expiration) : string
657
    {
658
        return (string) (new Builder())
659
            ->setIssuedAt($issuedAt->getTimestamp())
660
            ->setExpiration($expiration->getTimestamp())
661
            ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
662
            ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
663
            ->getToken();
664
    }
665
666
    /** @param mixed $claim */
667
    private function createTokenWithCustomClaim(
668
        SessionMiddleware $middleware,
669
        \DateTime $issuedAt,
670
        \DateTime $expiration,
671
        $claim
672
    ) : string {
673
        return (string) (new Builder())
674
            ->setIssuedAt($issuedAt->getTimestamp())
675
            ->setExpiration($expiration->getTimestamp())
676
            ->set(SessionMiddleware::SESSION_CLAIM, $claim)
677
            ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
678
            ->getToken();
679
    }
680
681
    private function emptyValidationMiddleware() : RequestHandlerInterface
682
    {
683
        return $this->fakeDelegate(
684
            function (ServerRequestInterface $request) {
685
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
686
687
                self::assertInstanceOf(SessionInterface::class, $session);
688
                self::assertTrue($session->isEmpty());
689
690
                return new Response();
691
            }
692
        );
693
    }
694
695
    private function writingMiddleware(string $value = 'bar') : RequestHandlerInterface
696
    {
697
        return $this->fakeDelegate(
698
            function (ServerRequestInterface $request) use ($value) {
699
                /** @var SessionInterface $session */
700
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
701
                $session->set('foo', $value);
702
703
                return new Response();
704
            }
705
        );
706
    }
707
708
    private function fakeDelegate(callable $callback) : RequestHandlerInterface
709
    {
710
        $middleware = $this->createMock(RequestHandlerInterface::class);
711
712
        $middleware
713
            ->expects(self::once())
714
           ->method('handle')
715
           ->willReturnCallback($callback)
716
           ->with(self::isInstanceOf(RequestInterface::class));
717
718
        return $middleware;
719
    }
720
721
    /**
722
     *
723
     * @return ServerRequest
724
     */
725
    private function requestWithResponseCookies(ResponseInterface $response) : ServerRequestInterface
726
    {
727
        return (new ServerRequest())->withCookieParams([
728
            SessionMiddleware::DEFAULT_COOKIE => $this->getCookie($response)->getValue(),
729
        ]);
730
    }
731
732
    private function getCookie(ResponseInterface $response, string $name = SessionMiddleware::DEFAULT_COOKIE) : SetCookie
733
    {
734
        return FigResponseCookies::get($response, $name);
735
    }
736
737
    private function getSigner(SessionMiddleware $middleware) : Signer
738
    {
739
        return self::getObjectAttribute($middleware, 'signer');
740
    }
741
742
    private function getSignatureKey(SessionMiddleware $middleware) : string
743
    {
744
        return self::getObjectAttribute($middleware, 'signatureKey');
745
    }
746
747
    private static function privateKey() : string
748
    {
749
        $key = file_get_contents(__DIR__ . '/../../keys/private_key.pem');
750
751
        self::assertInternalType('string', $key);
752
753
        return $key;
754
    }
755
756
    private static function publicKey() : string
757
    {
758
        $key = file_get_contents(__DIR__ . '/../../keys/public_key.pem');
759
760
        self::assertInternalType('string', $key);
761
762
        return $key;
763
    }
764
}
765