Passed
Push — master ( c0f6da...b424db )
by Jelmer
04:57
created

LoadJwtSessionMiddleware::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 0
cts 19
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 17
nc 1
nop 8
crap 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
use PSR7Session\Session\DefaultSessionData;
17
use PSR7Session\Session\LazySession;
18
use PSR7Session\Session\SessionInterface as JwtSessionInterface;
19
use PSR7Session\Time\CurrentTimeProviderInterface;
20
use PSR7Session\Time\SystemCurrentTime;
21
22
class LoadJwtSessionMiddleware implements HttpMiddlewareInterface
23
{
24
    const ISSUED_AT_CLAIM      = 'iat';
25
    const SESSION_CLAIM        = 'session-data';
26
    const DEFAULT_COOKIE       = 'slsession';
27
    const DEFAULT_REFRESH_TIME = 60;
28
29
    /** @var  Signer */
30
    private $signer;
31
32
    /** @var  string */
33
    private $signatureKey;
34
35
    /** @var  string */
36
    private $verificationKey;
37
38
    /** @var  int */
39
    private $expirationTime;
40
41
    /** @var  int */
42
    private $refreshTime;
43
44
    /** @var  Parser */
45
    private $tokenParser;
46
47
    /** @var  SetCookie */
48
    private $defaultCookie;
49
50
    /** @var  CurrentTimeProviderInterface */
51
    private $currentTimeProvider;
52
53
    public function __construct(
54
        Signer $signer,
55
        string $signatureKey,
56
        string $verificationKey,
57
        SetCookie $defaultCookie,
58
        Parser $tokenParser,
59
        int $expirationTime,
60
        CurrentTimeProviderInterface $currentTimeProvider,
61
        int $refreshTime = self::DEFAULT_REFRESH_TIME
62
    ) {
63
        $this->signer              = $signer;
64
        $this->signatureKey        = $signatureKey;
65
        $this->verificationKey     = $verificationKey;
66
        $this->tokenParser         = $tokenParser;
67
        $this->defaultCookie       = clone $defaultCookie;
68
        $this->expirationTime      = $expirationTime;
69
        $this->currentTimeProvider = $currentTimeProvider;
70
        $this->refreshTime         = $refreshTime;
71
    }
72
73
    public static function fromAsymmetricKeyDefaults(
74
        string $privateRsaKey,
75
        string $publicRsaKey,
76
        int $expirationTime
77
    ) : LoadJwtSessionMiddleware {
78
        return new self(
79
            new Signer\Rsa\Sha256(),
80
            $privateRsaKey,
81
            $publicRsaKey,
82
            SetCookie::create(self::DEFAULT_COOKIE)
83
                ->withSecure(true)
84
                ->withHttpOnly(true)
85
                ->withPath('/'),
86
            new Parser(),
87
            $expirationTime,
88
            new SystemCurrentTime()
89
        );
90
    }
91
92
    public function process(ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
93
    {
94
        $token            = $this->parseToken($request);
95
        $sessionContainer = LazySession::fromContainerBuildingCallback(function () use ($token) : JwtSessionInterface {
96
            return $this->extractSessionContainer($token);
97
        });
98
99
        $response = $delegate->next($request->withAttribute('session', new JwtSession($sessionContainer)));
100
101
        return $this->appendToken($sessionContainer, $response, $token);
102
    }
103
104
    /** @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...
105
    private function parseToken(ServerRequestInterface $request)
106
    {
107
        $cookies    = $request->getCookieParams();
108
        $cookieName = $this->defaultCookie->getName();
109
110
        if (! isset($cookies[$cookieName])) {
111
            return null;
112
        }
113
114
        try {
115
            $token = $this->tokenParser->parse($cookies[$cookieName]);
116
        } catch (\InvalidArgumentException $invalidToken) {
117
            return null;
118
        }
119
120
        if (! $token->validate(new ValidationData())) {
121
            return null;
122
        }
123
124
        return $token;
125
    }
126
127
    public function extractSessionContainer(Token $token = null) : JwtSessionInterface
128
    {
129
        try {
130
            if (null === $token || ! $token->verify($this->signer, $this->verificationKey)) {
131
                return DefaultSessionData::newEmptySession();
132
            }
133
134
            return DefaultSessionData::fromDecodedTokenData(
135
                (object) $token->getClaim(self::SESSION_CLAIM, new \stdClass())
136
            );
137
        } catch (\BadMethodCallException $invalidToken) {
138
            return DefaultSessionData::newEmptySession();
139
        }
140
    }
141
142
    private function appendToken(
143
        JwtSessionInterface $sessionContainer,
144
        ResponseInterface $response,
145
        Token $token = null
146
    ) : ResponseInterface
147
    {
148
        $sessionContainerChanged = $sessionContainer->hasChanged();
149
150
        if ($sessionContainerChanged && $sessionContainer->isEmpty()) {
151
            return FigResponseCookies::set($response, $this->getExpirationCookie());
152
        }
153
154
        if ($sessionContainerChanged || ($this->shouldTokenBeRefreshed($token) && ! $sessionContainer->isEmpty())) {
155
            return FigResponseCookies::set($response, $this->getTokenCookie($sessionContainer));
156
        }
157
158
        return $response;
159
    }
160
161
    /**
162
     * {@inheritDoc}
163
     */
164
    private function shouldTokenBeRefreshed(Token $token = null) : bool
165
    {
166
        if (null === $token) {
167
            return false;
168
        }
169
170
        if (! $token->hasClaim(self::ISSUED_AT_CLAIM)) {
171
            return false;
172
        }
173
174
        return $this->timestamp() >= ($token->getClaim(self::ISSUED_AT_CLAIM) + $this->refreshTime);
175
    }
176
177
    private function getTokenCookie(JwtSessionInterface $sessionContainer) : SetCookie
178
    {
179
        $timestamp = $this->timestamp();
180
181
        return $this
182
            ->defaultCookie
183
            ->withValue(
184
                (new Builder())
185
                    ->setIssuedAt($timestamp)
186
                    ->setExpiration($timestamp + $this->expirationTime)
187
                    ->set(self::SESSION_CLAIM, $sessionContainer)
188
                    ->sign($this->signer, $this->signatureKey)
189
                    ->getToken()
190
            )
191
            ->withExpires($timestamp + $this->expirationTime);
192
    }
193
194
    /**
195
     * @return SetCookie
196
     */
197
    private function getExpirationCookie() : SetCookie
198
    {
199
        $currentTimeProvider = $this->currentTimeProvider;
200
        $expirationDate      = $currentTimeProvider();
201
        $expirationDate      = $expirationDate->modify('-30 days');
202
203
        return $this
204
            ->defaultCookie
205
            ->withValue(null)
206
            ->withExpires($expirationDate->getTimestamp());
207
    }
208
209
    private function timestamp() : int
210
    {
211
        $currentTimeProvider = $this->currentTimeProvider;
212
213
        return $currentTimeProvider()->getTimestamp();
214
    }
215
}
216