Completed
Push — master ( e05cd4...bb1ac9 )
by Luís
10s
created

SessionMiddlewareTest::getCookie()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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\SetCookie;
26
use Lcobucci\JWT\Builder;
27
use Lcobucci\JWT\Parser;
28
use Lcobucci\JWT\Signer;
29
use Lcobucci\JWT\Signer\Hmac\Sha256;
30
use Lcobucci\JWT\Token;
31
use PHPUnit_Framework_TestCase;
32
use Psr\Http\Message\ResponseInterface;
33
use Psr\Http\Message\ServerRequestInterface;
34
use PSR7Sessions\Storageless\Http\SessionMiddleware;
35
use PSR7Sessions\Storageless\Session\DefaultSessionData;
36
use PSR7Sessions\Storageless\Session\SessionInterface;
37
use PSR7Sessions\Storageless\Time\CurrentTimeProviderInterface;
38
use PSR7Sessions\Storageless\Time\SystemCurrentTime;
39
use PSR7SessionsTest\Storageless\Time\FakeCurrentTime;
40
use Zend\Diactoros\Response;
41
use Zend\Diactoros\ServerRequest;
42
use Zend\Stratigility\MiddlewareInterface;
43
44
final class SessionMiddlewareTest extends PHPUnit_Framework_TestCase
45
{
46
    public function testFromSymmetricKeyDefaultsUsesASecureCookie()
47
    {
48
        $response = SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)
49
            ->__invoke(new ServerRequest(), new Response(), $this->writingMiddleware());
50
51
        $cookie = $this->getCookie($response);
0 ignored issues
show
Bug introduced by
It seems like $response defined by \PSR7Sessions\Storageles...s->writingMiddleware()) on line 48 can be null; however, PSR7SessionsTest\Storage...lewareTest::getCookie() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
52
53
        self::assertTrue($cookie->getSecure());
54
        self::assertTrue($cookie->getHttpOnly());
55
    }
56
57
    public function testFromAsymmetricKeyDefaultsUsesASecureCookie()
58
    {
59
        $response = SessionMiddleware
60
            ::fromAsymmetricKeyDefaults(
61
                file_get_contents(__DIR__ . '/../../keys/private_key.pem'),
62
                file_get_contents(__DIR__ . '/../../keys/public_key.pem'),
63
                200
64
            )
65
            ->__invoke(new ServerRequest(), new Response(), $this->writingMiddleware());
66
67
        $cookie = $this->getCookie($response);
0 ignored issues
show
Bug introduced by
It seems like $response defined by \PSR7Sessions\Storageles...s->writingMiddleware()) on line 59 can be null; however, PSR7SessionsTest\Storage...lewareTest::getCookie() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
68
69
        self::assertTrue($cookie->getSecure());
70
        self::assertTrue($cookie->getHttpOnly());
71
    }
72
73
    /**
74
     * @dataProvider validMiddlewaresProvider
75
     */
76
    public function testSkipsInjectingSessionCookieOnEmptyContainer(SessionMiddleware $middleware)
77
    {
78
        $response = $this->ensureSameResponse($middleware, new ServerRequest(), $this->emptyValidationMiddleware());
79
80
        self::assertNull($this->getCookie($response)->getValue());
81
    }
82
83
    /**
84
     * @dataProvider validMiddlewaresProvider
85
     */
86
    public function testExtractsSessionContainerFromEmptyRequest(SessionMiddleware $middleware)
87
    {
88
        $this->ensureSameResponse($middleware, new ServerRequest(), $this->emptyValidationMiddleware());
89
    }
90
91
    /**
92
     * @dataProvider validMiddlewaresProvider
93
     */
94
    public function testInjectsSessionInResponseCookies(SessionMiddleware $middleware)
95
    {
96
        $initialResponse = new Response();
97
        $response = $middleware(new ServerRequest(), $initialResponse, $this->writingMiddleware());
98
99
        self::assertNotSame($initialResponse, $response);
100
        self::assertEmpty($this->getCookie($response, 'non-existing')->getValue());
101
        self::assertInstanceOf(Token::class, (new Parser())->parse($this->getCookie($response)->getValue()));
102
    }
103
104
    /**
105
     * @dataProvider validMiddlewaresProvider
106
     */
107
    public function testSessionContainerCanBeReusedOverMultipleRequests(SessionMiddleware $middleware)
108
    {
109
        $sessionValue = uniqid('', true);
110
111
        $checkingMiddleware = $this->fakeMiddleware(
112
            function (ServerRequestInterface $request, ResponseInterface $response) use ($sessionValue) {
113
                /* @var $session SessionInterface */
114
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
115
116
                self::assertSame($sessionValue, $session->get('foo'));
117
                self::assertFalse($session->hasChanged());
118
119
                $session->set('foo', $sessionValue . 'changed');
120
121
                self::assertTrue(
122
                    $session->hasChanged(),
123
                    'ensuring that the cookie is sent again: '
124
                    . 'non-modified session containers are not to be re-serialized into a token'
125
                );
126
127
                return $response;
128
            }
129
        );
130
131
        $firstResponse = $middleware(new ServerRequest(), new Response(), $this->writingMiddleware($sessionValue));
132
133
        $initialResponse = new Response();
134
135
        $response = $middleware(
136
            $this->requestWithResponseCookies($firstResponse),
137
            $initialResponse,
138
            $checkingMiddleware
139
        );
140
141
        self::assertNotSame($initialResponse, $response);
142
    }
143
144
    /**
145
     * @dataProvider validMiddlewaresProvider
146
     */
147
    public function testWillIgnoreRequestsWithExpiredTokens(SessionMiddleware $middleware)
148
    {
149
        $expiredToken = (new ServerRequest())
150
            ->withCookieParams([
151
                SessionMiddleware::DEFAULT_COOKIE => $this->createToken(
152
                    $middleware,
153
                    new \DateTime('-1 day'),
154
                    new \DateTime('-2 day')
155
                )
156
            ]);
157
158
        $this->ensureSameResponse($middleware, $expiredToken, $this->emptyValidationMiddleware());
159
    }
160
161
    /**
162
     * @dataProvider validMiddlewaresProvider
163
     */
164
    public function testWillIgnoreRequestsWithTokensFromFuture(SessionMiddleware $middleware)
165
    {
166
        $tokenInFuture = (new ServerRequest())
167
            ->withCookieParams([
168
                SessionMiddleware::DEFAULT_COOKIE => $this->createToken(
169
                    $middleware,
170
                    new \DateTime('+1 day'),
171
                    new \DateTime('-2 day')
172
                )
173
            ]);
174
175
        $this->ensureSameResponse($middleware, $tokenInFuture, $this->emptyValidationMiddleware());
176
    }
177
178
    /**
179
     * @dataProvider validMiddlewaresProvider
180
     */
181
    public function testWillIgnoreUnSignedTokens(SessionMiddleware $middleware)
182
    {
183
        $unsignedToken = (new ServerRequest())
184
            ->withCookieParams([
185
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
186
                    ->setIssuedAt((new \DateTime('-1 day'))->getTimestamp())
187
                    ->setExpiration((new \DateTime('+1 day'))->getTimestamp())
188
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
189
                    ->getToken()
190
            ]);
191
192
        $this->ensureSameResponse($middleware, $unsignedToken, $this->emptyValidationMiddleware());
193
    }
194
195
    /**
196
     * @dataProvider validMiddlewaresProvider
197
     */
198
    public function testWillNotRefreshSignedTokensWithoutIssuedAt(SessionMiddleware $middleware)
199
    {
200
        $unsignedToken = (new ServerRequest())
201
            ->withCookieParams([
202
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
203
                    ->setExpiration((new \DateTime('+1 day'))->getTimestamp())
204
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
205
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
206
                    ->getToken()
207
            ]);
208
209
        $this->ensureSameResponse($middleware, $unsignedToken);
210
    }
211
212
    public function testWillRefreshTokenWithIssuedAtExactlyAtTokenRefreshTimeThreshold()
213
    {
214
        /* @var $currentTimeProvider CurrentTimeProviderInterface|\PHPUnit_Framework_MockObject_MockObject */
215
        $currentTimeProvider = $this->createMock(CurrentTimeProviderInterface::class);
216
217
        $middleware = new SessionMiddleware(
218
            new Sha256(),
219
            'foo',
220
            'foo',
221
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
222
            new Parser(),
223
            1000,
224
            $currentTimeProvider,
0 ignored issues
show
Bug introduced by
It seems like $currentTimeProvider defined by $this->createMock(\PSR7S...oviderInterface::class) on line 215 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, PSR7Sessions\Storageless...ddleware::__construct() does only seem to accept object<PSR7Sessions\Stor...tTimeProviderInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
225
            100
226
        );
227
228
        // forcing ourselves to think of time as a mutable value:
229
        $time = time() + random_int(-100, +100);
230
231
        $currentTimeProvider->expects(self::any())->method('__invoke')->willReturn(new \DateTimeImmutable('@' . $time));
0 ignored issues
show
Bug introduced by
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in PSR7Sessions\Storageless...ntTimeProviderInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
232
233
        $requestWithTokenIssuedInThePast = (new ServerRequest())
234
            ->withCookieParams([
235
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
236
                    ->setExpiration($time + 10000)
237
                    ->setIssuedAt($time - 100)
238
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
239
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
240
                    ->getToken()
241
            ]);
242
243
        $cookie = $this->getCookie($middleware->__invoke($requestWithTokenIssuedInThePast, new Response()));
0 ignored issues
show
Bug introduced by
It seems like $middleware->__invoke($r...d\Diactoros\Response()) can be null; however, getCookie() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
244
245
        $token = (new Parser())->parse($cookie->getValue());
246
247
        self::assertEquals($time, $token->getClaim(SessionMiddleware::ISSUED_AT_CLAIM), 'Token was refreshed');
248
    }
249
250
    /**
251
     * @dataProvider validMiddlewaresProvider
252
     */
253
    public function testWillSkipInjectingSessionCookiesWhenSessionIsNotChanged(SessionMiddleware $middleware)
254
    {
255
        $this->ensureSameResponse(
256
            $middleware,
257
            $this->requestWithResponseCookies(
258
                $middleware(new ServerRequest(), new Response(), $this->writingMiddleware())
259
            ),
260
            $this->fakeMiddleware(
261
                function (ServerRequestInterface $request, ResponseInterface $response) {
262
                    /* @var $session SessionInterface */
263
                    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
264
265
                    // note: we set the same data just to make sure that we are indeed interacting with the session
266
                    $session->set('foo', 'bar');
267
268
                    self::assertFalse($session->hasChanged());
269
270
                    return $response;
271
                }
272
            )
273
        );
274
    }
275
276
    /**
277
     * @dataProvider validMiddlewaresProvider
278
     */
279
    public function testWillSendExpirationCookieWhenSessionContentsAreCleared(SessionMiddleware $middleware)
280
    {
281
        $this->ensureClearsSessionCookie(
282
            $middleware,
283
            $this->requestWithResponseCookies(
284
                $middleware(new ServerRequest(), new Response(), $this->writingMiddleware())
285
            ),
286
            $this->fakeMiddleware(
287
                function (ServerRequestInterface $request, ResponseInterface $response) {
288
                    /* @var $session SessionInterface */
289
                    $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
290
291
                    $session->clear();
292
293
                    return $response;
294
                }
295
            )
296
        );
297
    }
298
299
    /**
300
     * @dataProvider validMiddlewaresProvider
301
     */
302
    public function testWillIgnoreMalformedTokens(SessionMiddleware $middleware)
303
    {
304
        $this->ensureSameResponse(
305
            $middleware,
306
            (new ServerRequest())->withCookieParams([SessionMiddleware::DEFAULT_COOKIE => 'malformed content']),
307
            $this->emptyValidationMiddleware()
308
        );
309
    }
310
311
    public function testRejectsTokensWithInvalidSignature()
312
    {
313
        $middleware = new SessionMiddleware(
314
            new Sha256(),
315
            'foo',
316
            'bar', // wrong symmetric key (on purpose)
317
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
318
            new Parser(),
319
            100,
320
            new SystemCurrentTime()
321
        );
322
323
        $this->ensureSameResponse(
324
            $middleware,
325
            $this->requestWithResponseCookies(
326
                $middleware(new ServerRequest(), new Response(), $this->writingMiddleware())
327
            ),
328
            $this->emptyValidationMiddleware()
329
        );
330
    }
331
332
    public function testAllowsModifyingCookieDetails()
333
    {
334
        $defaultCookie = SetCookie::create('a-different-cookie-name')
335
            ->withDomain('foo.bar')
336
            ->withPath('/yadda')
337
            ->withHttpOnly(false)
338
            ->withMaxAge('123123')
339
            ->withValue('a-random-value');
340
341
        $dateTime   = new DateTimeImmutable();
342
        $middleware = new SessionMiddleware(
343
            new Sha256(),
344
            'foo',
345
            'foo',
346
            $defaultCookie,
347
            new Parser(),
348
            123456,
349
            new FakeCurrentTime($dateTime),
350
            123
351
        );
352
353
        $initialResponse = new Response();
354
        $response = $middleware(new ServerRequest(), $initialResponse, $this->writingMiddleware());
355
356
        self::assertNotSame($initialResponse, $response);
357
        self::assertNull($this->getCookie($response)->getValue());
358
359
        $tokenCookie = $this->getCookie($response, 'a-different-cookie-name');
360
361
        self::assertNotEmpty($tokenCookie->getValue());
362
        self::assertNotSame($defaultCookie->getValue(), $tokenCookie->getValue());
363
        self::assertSame($defaultCookie->getDomain(), $tokenCookie->getDomain());
364
        self::assertSame($defaultCookie->getPath(), $tokenCookie->getPath());
365
        self::assertSame($defaultCookie->getHttpOnly(), $tokenCookie->getHttpOnly());
366
        self::assertSame($defaultCookie->getMaxAge(), $tokenCookie->getMaxAge());
367
        self::assertEquals($dateTime->getTimestamp() + 123456, $tokenCookie->getExpires());
368
    }
369
370
    public function testSessionTokenParsingIsDelayedWhenSessionIsNotBeingUsed()
371
    {
372
        /* @var $signer Signer|\PHPUnit_Framework_MockObject_MockObject */
373
        $signer = $this->createMock(Signer::class);
374
375
        $signer->expects($this->never())->method('verify');
0 ignored issues
show
Bug introduced by
The method expects does only exist in PHPUnit_Framework_MockObject_MockObject, but not in Lcobucci\JWT\Signer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
376
377
        $currentTimeProvider = new SystemCurrentTime();
378
        $setCookie  = SetCookie::create(SessionMiddleware::DEFAULT_COOKIE);
379
        $middleware = new SessionMiddleware($signer, 'foo', 'foo', $setCookie, new Parser(), 100, $currentTimeProvider);
0 ignored issues
show
Bug introduced by
It seems like $signer defined by $this->createMock(\Lcobucci\JWT\Signer::class) on line 373 can also be of type object<PHPUnit_Framework_MockObject_MockObject>; however, PSR7Sessions\Storageless...ddleware::__construct() does only seem to accept object<Lcobucci\JWT\Signer>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
380
        $request    = (new ServerRequest())
381
            ->withCookieParams([
382
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
383
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
384
                    ->getToken()
385
            ]);
386
387
        $middleware(
388
            $request,
389
            new Response(),
390
            $this->fakeMiddleware(function (ServerRequestInterface $request, ResponseInterface $response) {
391
                self::assertInstanceOf(
392
                    SessionInterface::class,
393
                    $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE)
394
                );
395
396
                return $response;
397
            })
398
        );
399
    }
400
401
    public function testShouldRegenerateTokenWhenRequestHasATokenThatIsAboutToExpire()
402
    {
403
        $dateTime   = new DateTimeImmutable();
404
        $middleware = new SessionMiddleware(
405
            new Sha256(),
406
            'foo',
407
            'foo',
408
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
409
            new Parser(),
410
            1000,
411
            new FakeCurrentTime($dateTime),
412
            300
413
        );
414
415
        $expiringToken = (new ServerRequest())
416
            ->withCookieParams([
417
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
418
                    ->setIssuedAt((new \DateTime('-800 second'))->getTimestamp())
419
                    ->setExpiration((new \DateTime('+200 second'))->getTimestamp())
420
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
421
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
422
                    ->getToken()
423
            ]);
424
425
        $initialResponse = new Response();
426
        $response = $middleware($expiringToken, $initialResponse);
427
428
        self::assertNotSame($initialResponse, $response);
429
430
        $tokenCookie = $this->getCookie($response);
431
432
        self::assertNotEmpty($tokenCookie->getValue());
433
        self::assertEquals($dateTime->getTimestamp() + 1000, $tokenCookie->getExpires());
434
    }
435
436
    public function testShouldNotRegenerateTokenWhenRequestHasATokenThatIsFarFromExpiration()
437
    {
438
        $middleware = new SessionMiddleware(
439
            new Sha256(),
440
            'foo',
441
            'foo',
442
            SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
443
            new Parser(),
444
            1000,
445
            new SystemCurrentTime(),
446
            300
447
        );
448
449
        $validToken = (new ServerRequest())
450
            ->withCookieParams([
451
                SessionMiddleware::DEFAULT_COOKIE => (string) (new Builder())
452
                    ->setIssuedAt((new \DateTime('-100 second'))->getTimestamp())
453
                    ->setExpiration((new \DateTime('+900 second'))->getTimestamp())
454
                    ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
455
                    ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
456
                    ->getToken()
457
            ]);
458
459
        $this->ensureSameResponse($middleware, $validToken);
460
    }
461
462
    /**
463
     * @return SessionMiddleware[][]
464
     */
465
    public function validMiddlewaresProvider()
466
    {
467
        return [
468
            [new SessionMiddleware(
469
                new Sha256(),
470
                'foo',
471
                'foo',
472
                SetCookie::create(SessionMiddleware::DEFAULT_COOKIE),
473
                new Parser(),
474
                100,
475
                new SystemCurrentTime()
476
            )],
477
            [SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)],
478
            [SessionMiddleware::fromAsymmetricKeyDefaults(
479
                file_get_contents(__DIR__ . '/../../keys/private_key.pem'),
480
                file_get_contents(__DIR__ . '/../../keys/public_key.pem'),
481
                200
482
            )],
483
        ];
484
    }
485
486
    /**
487
     * @group #46
488
     */
489
    public function testFromSymmetricKeyDefaultsWillHaveADefaultSessionPath()
490
    {
491
        self::assertSame(
492
            '/',
493
            $this
494
                ->getCookie(
495
                    SessionMiddleware::fromSymmetricKeyDefaults('not relevant', 100)
0 ignored issues
show
Bug introduced by
It seems like \PSR7Sessions\Storageles...s->writingMiddleware()) can be null; however, getCookie() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
496
                        ->__invoke(new ServerRequest(), new Response(), $this->writingMiddleware())
497
                )
498
                ->getPath()
499
        );
500
    }
501
502
    /**
503
     * @group #46
504
     *
505
     * @throws \InvalidArgumentException
506
     * @throws \OutOfBoundsException
507
     */
508
    public function testFromAsymmetricKeyDefaultsWillHaveADefaultSessionPath()
509
    {
510
        self::assertSame(
511
            '/',
512
            $this
513
                ->getCookie(
514
                    SessionMiddleware
0 ignored issues
show
Bug introduced by
It seems like \PSR7Sessions\Storageles...s->writingMiddleware()) can be null; however, getCookie() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
515
                        ::fromAsymmetricKeyDefaults(
516
                            file_get_contents(__DIR__ . '/../../keys/private_key.pem'),
517
                            file_get_contents(__DIR__ . '/../../keys/public_key.pem'),
518
                            200
519
                        )
520
                        ->__invoke(new ServerRequest(), new Response(), $this->writingMiddleware())
521
                )
522
                ->getPath()
523
        );
524
    }
525
526
    /**
527
     * @param SessionMiddleware $middleware
528
     * @param ServerRequestInterface $request
529
     * @param callable $next
530
     *
531
     * @return ResponseInterface
532
     */
533
    private function ensureSameResponse(
534
        SessionMiddleware $middleware,
535
        ServerRequestInterface $request,
536
        callable $next = null
537
    ): ResponseInterface {
538
        $initialResponse = new Response();
539
        $response = $middleware($request, $initialResponse, $next);
540
541
        self::assertSame($initialResponse, $response);
542
543
        return $response;
544
    }
545
546
    /**
547
     * @param SessionMiddleware $middleware
548
     * @param ServerRequestInterface $request
549
     * @param callable $next
550
     *
551
     * @return ResponseInterface
552
     */
553
    private function ensureClearsSessionCookie(
554
        SessionMiddleware $middleware,
555
        ServerRequestInterface $request,
556
        callable $next = null
557
    ): ResponseInterface {
558
        $initialResponse = new Response();
559
        $response = $middleware($request, $initialResponse, $next);
560
561
        self::assertNotSame($initialResponse, $response);
562
563
        $cookie = $this->getCookie($response);
564
565
        self::assertLessThan((new \DateTime('-29 day'))->getTimestamp(), $cookie->getExpires());
566
        self::assertEmpty($cookie->getValue());
567
568
        return $response;
569
    }
570
571
    /**
572
     * @param SessionMiddleware $middleware
573
     * @param \DateTime $issuedAt
574
     * @param \DateTime $expiration
575
     *
576
     * @return string
577
     */
578
    private function createToken(SessionMiddleware $middleware, \DateTime $issuedAt, \DateTime $expiration): string
579
    {
580
        return (string) (new Builder())
581
            ->setIssuedAt($issuedAt->getTimestamp())
582
            ->setExpiration($expiration->getTimestamp())
583
            ->set(SessionMiddleware::SESSION_CLAIM, DefaultSessionData::fromTokenData(['foo' => 'bar']))
584
            ->sign($this->getSigner($middleware), $this->getSignatureKey($middleware))
585
            ->getToken();
586
    }
587
588
    /**
589
     * @return MiddlewareInterface
590
     */
591
    private function emptyValidationMiddleware(): MiddlewareInterface
592
    {
593
        return $this->fakeMiddleware(
594
            function (ServerRequestInterface $request, ResponseInterface $response) {
595
                /* @var $session SessionInterface */
596
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
597
598
                self::assertInstanceOf(SessionInterface::class, $session);
599
                self::assertTrue($session->isEmpty());
600
601
                return $response;
602
            }
603
        );
604
    }
605
606
    /**
607
     * @param string $value
608
     *
609
     * @return MiddlewareInterface
610
     */
611
    private function writingMiddleware($value = 'bar'): MiddlewareInterface
612
    {
613
        return $this->fakeMiddleware(
614
            function (ServerRequestInterface $request, ResponseInterface $response) use ($value) {
615
                /* @var $session SessionInterface */
616
                $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
617
                $session->set('foo', $value);
618
619
                return $response;
620
            }
621
        );
622
    }
623
624
    /**
625
     * @param callable $callback
626
     *
627
     * @return MiddlewareInterface
628
     */
629
    private function fakeMiddleware(callable $callback): MiddlewareInterface
630
    {
631
        $middleware = $this->createMock(MiddlewareInterface::class);
632
633
        $middleware->expects($this->once())
634
           ->method('__invoke')
635
           ->willReturnCallback($callback)
636
           ->with(
637
               self::isInstanceOf(ServerRequestInterface::class),
638
               self::isInstanceOf(ResponseInterface::class),
639
               self::logicalOr(self::isNull(), self::isType('callable'))
640
           );
641
642
        return $middleware;
643
    }
644
645
    /**
646
     * @param ResponseInterface $response
647
     *
648
     * @return \Zend\Diactoros\ServerRequest
649
     */
650
    private function requestWithResponseCookies(ResponseInterface $response): ServerRequestInterface
651
    {
652
        return (new ServerRequest())->withCookieParams([
653
            SessionMiddleware::DEFAULT_COOKIE => $this->getCookie($response)->getValue()
654
        ]);
655
    }
656
657
    /**
658
     * @param ResponseInterface $response
659
     *
660
     * @return SetCookie
661
     */
662
    private function getCookie(ResponseInterface $response, string $name = SessionMiddleware::DEFAULT_COOKIE): SetCookie
663
    {
664
        return FigResponseCookies::get($response, $name);
665
    }
666
667
    /**
668
     * @param SessionMiddleware $middleware
669
     *
670
     * @return Signer
671
     */
672
    private function getSigner(SessionMiddleware $middleware): Signer
673
    {
674
        return $this->getPropertyValue($middleware, 'signer');
675
    }
676
677
    /**
678
     * @param SessionMiddleware $middleware
679
     *
680
     * @return string
681
     */
682
    private function getSignatureKey(SessionMiddleware $middleware): string
683
    {
684
        return $this->getPropertyValue($middleware, 'signatureKey');
685
    }
686
687
    /**
688
     * @param object $object
689
     * @param string $name
690
     *
691
     * @return mixed
692
     */
693
    private function getPropertyValue($object, string $name)
694
    {
695
        $propertyReflection = new \ReflectionProperty($object, $name);
696
        $propertyReflection->setAccessible(true);
697
698
        return $propertyReflection->getValue($object);
699
    }
700
}
701