Passed
Push — master ( b424db...12a61c )
by Jelmer
03:50
created

LoadJwtSessionMiddleware::timestamp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
crap 2
1
<?php declare(strict_types = 1);
2
3
namespace jschreuder\Middle\Session;
4
5
use Dflydev\FigCookies\FigResponseCookies;
6
use Dflydev\FigCookies\SetCookie;
7
use jschreuder\Middle\DelegateInterface;
8
use jschreuder\Middle\HttpMiddlewareInterface;
9
use Lcobucci\JWT\Builder;
10
use Lcobucci\JWT\Parser;
11
use Lcobucci\JWT\Signer;
12
use Lcobucci\JWT\Token;
13
use Lcobucci\JWT\ValidationData;
14
use Psr\Http\Message\ResponseInterface;
15
use Psr\Http\Message\ServerRequestInterface;
16
17
/**
18
 * A copy-paste from ocramius/psr7-session, but without needing to pull in
19
 * Stratigility and working with our session container.
20
 */
21
class LoadJwtSessionMiddleware implements HttpMiddlewareInterface
22
{
23
    const ISSUED_AT_CLAIM      = 'iat';
24
    const SESSION_CLAIM        = 'session-data';
25
    const DEFAULT_COOKIE       = 'slsession';
26
    const DEFAULT_REFRESH_TIME = 60;
27
28
    /** @var  Signer */
29
    private $signer;
30
31
    /** @var  string */
32
    private $signatureKey;
33
34
    /** @var  string */
35
    private $verificationKey;
36
37
    /** @var  int */
38
    private $expirationTime;
39
40
    /** @var  int */
41
    private $refreshTime;
42
43
    /** @var  Parser */
44
    private $tokenParser;
45
46
    /** @var  SetCookie */
47
    private $defaultCookie;
48
49
    public function __construct(
50
        Signer $signer,
51
        string $signatureKey,
52
        string $verificationKey,
53
        SetCookie $defaultCookie,
54
        Parser $tokenParser,
55
        int $expirationTime,
56
        int $refreshTime = self::DEFAULT_REFRESH_TIME
57
    ) {
58
        $this->signer              = $signer;
59
        $this->signatureKey        = $signatureKey;
60
        $this->verificationKey     = $verificationKey;
61
        $this->tokenParser         = $tokenParser;
62
        $this->defaultCookie       = clone $defaultCookie;
63
        $this->expirationTime      = $expirationTime;
64
        $this->refreshTime         = $refreshTime;
65
    }
66
67
    public static function fromAsymmetricKeyDefaults(
68
        string $privateRsaKey,
69
        string $publicRsaKey,
70
        int $expirationTime
71
    ) : LoadJwtSessionMiddleware {
72
        return new self(
73
            new Signer\Rsa\Sha256(),
74
            $privateRsaKey,
75
            $publicRsaKey,
76
            SetCookie::create(self::DEFAULT_COOKIE)
77
                ->withSecure(true)
78
                ->withHttpOnly(true)
79
                ->withPath('/'),
80
            new Parser(),
81
            $expirationTime
82
        );
83
    }
84
85
    public function process(ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
86
    {
87
        $token = $this->parseToken($request);
88
        $sessionContainer = $this->extractSessionContainer($token);
89
90
        $response = $delegate->next($request->withAttribute('session', new GenericSession($sessionContainer)));
0 ignored issues
show
Documentation introduced by
$sessionContainer is of type object<jschreuder\Middle...ssion\SessionInterface>, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
91
92
        return $this->appendToken($sessionContainer, $response, $token);
93
    }
94
95
    /** @return  ?Token */
1 ignored issue
show
Documentation introduced by
The doc-type ?Token could not be parsed: Unknown type name "?Token" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
96
    private function parseToken(ServerRequestInterface $request)
97
    {
98
        $cookies    = $request->getCookieParams();
99
        $cookieName = $this->defaultCookie->getName();
100
101
        if (!isset($cookies[$cookieName])) {
102
            return null;
103
        }
104
105
        try {
106
            $token = $this->tokenParser->parse($cookies[$cookieName]);
107
        } catch (\InvalidArgumentException $invalidToken) {
108
            return null;
109
        }
110
111
        if (!$token->validate(new ValidationData())) {
112
            return null;
113
        }
114
115
        return $token;
116
    }
117
118
    private function extractSessionContainer(Token $token = null) : SessionInterface
119
    {
120
        try {
121
            if (is_null($token) || !$token->verify($this->signer, $this->verificationKey)) {
122
                return new GenericSession();
123
            }
124
125
            return new GenericSession($token->getPayload());
0 ignored issues
show
Documentation introduced by
$token->getPayload() is of type string, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
126
        } catch (\BadMethodCallException $invalidToken) {
127
            return new GenericSession();
128
        }
129
    }
130
131
    private function appendToken(
132
        SessionInterface $sessionContainer,
133
        ResponseInterface $response,
134
        Token $token = null
135
    ) : ResponseInterface
136
    {
137
        $sessionContainerChanged = $sessionContainer->hasChanged();
138
139
        if ($sessionContainerChanged && $sessionContainer->isEmpty()) {
140
            return FigResponseCookies::set($response, $this->getExpirationCookie());
141
        }
142
143
        if ($sessionContainerChanged || ($this->shouldTokenBeRefreshed($token) && ! $sessionContainer->isEmpty())) {
144
            return FigResponseCookies::set($response, $this->getTokenCookie($sessionContainer));
145
        }
146
147
        return $response;
148
    }
149
150
    private function shouldTokenBeRefreshed(Token $token = null) : bool
151
    {
152
        if (is_null($token)) {
153
            return false;
154
        }
155
156
        if (!$token->hasClaim(self::ISSUED_AT_CLAIM)) {
157
            return false;
158
        }
159
160
        return time() >= ($token->getClaim(self::ISSUED_AT_CLAIM) + $this->refreshTime);
161
    }
162
163
    private function getTokenCookie(SessionInterface $sessionContainer) : SetCookie
164
    {
165
        $timestamp = time();
166
        return $this
167
            ->defaultCookie
168
            ->withValue(
169
                (new Builder())
170
                    ->setIssuedAt($timestamp)
171
                    ->setExpiration($timestamp + $this->expirationTime)
172
                    ->set(self::SESSION_CLAIM, $sessionContainer)
173
                    ->sign($this->signer, $this->signatureKey)
174
                    ->getToken()
175
            )
176
            ->withExpires($timestamp + $this->expirationTime);
177
    }
178
179
    private function getExpirationCookie() : SetCookie
180
    {
181
        $expirationDate = new \DateTime('-30 days');
182
        return $this
183
            ->defaultCookie
184
            ->withValue(null)
185
            ->withExpires($expirationDate->getTimestamp());
186
    }
187
}
188