Passed
Push — master ( 34b3f7...61b2d3 )
by Jelmer
03:08
created

JwtToPsrMapper::parseToken()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 0
cts 11
cp 0
rs 9.0534
c 0
b 0
f 0
cc 4
eloc 12
nc 4
nop 1
crap 20
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 Lcobucci\JWT\Builder;
8
use Lcobucci\JWT\Parser;
9
use Lcobucci\JWT\Signer;
10
use Lcobucci\JWT\Token;
11
use Lcobucci\JWT\ValidationData;
12
use Psr\Http\Message\ResponseInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
15
/**
16
 * A copy-paste from ocramius/psr7-session, but without needing to pull in
17
 * Stratigility and working with our session container.
18
 */
19
final class JwtToPsrMapper implements JwtToPsrMapperInterface
20
{
21
    /** @var  Signer */
22
    private $signer;
23
24
    /** @var  string */
25
    private $signatureKey;
26
27
    /** @var  string */
28
    private $verificationKey;
29
30
    /** @var  int */
31
    private $expirationTime;
32
33
    /** @var  int */
34
    private $refreshTime;
35
36
    /** @var  Parser */
37
    private $tokenParser;
38
39
    /** @var  SetCookie */
40
    private $defaultCookie;
41
42 1
    private function __construct(
43
        Signer $signer,
44
        string $signatureKey,
45
        string $verificationKey,
46
        SetCookie $defaultCookie,
47
        Parser $tokenParser,
48
        int $expirationTime,
49
        int $refreshTime = 60
50
    ) {
51 1
        $this->signer              = $signer;
52 1
        $this->signatureKey        = $signatureKey;
53 1
        $this->verificationKey     = $verificationKey;
54 1
        $this->tokenParser         = $tokenParser;
55 1
        $this->defaultCookie       = clone $defaultCookie;
56 1
        $this->expirationTime      = $expirationTime;
57 1
        $this->refreshTime         = $refreshTime;
58 1
    }
59
60 1
    public static function fromAsymmetricKeyDefaults(
61
        string $privateRsaKey,
62
        string $publicRsaKey,
63
        int $expirationTime
64
    ) : JwtToPsrMapper {
65 1
        return new self(
66 1
            new Signer\Rsa\Sha256(),
67
            $privateRsaKey,
68
            $publicRsaKey,
69 1
            SetCookie::create(self::DEFAULT_COOKIE)
70 1
                ->withSecure(true)
71 1
                ->withHttpOnly(true)
72 1
                ->withPath('/'),
73 1
            new Parser(),
74
            $expirationTime
75
        );
76
    }
77
78
    /** @return  ?Token */
79
    public function parseToken(ServerRequestInterface $request)
80
    {
81
        $cookies    = $request->getCookieParams();
82
        $cookieName = $this->defaultCookie->getName();
83
84
        if (!isset($cookies[$cookieName])) {
85
            return null;
86
        }
87
88
        try {
89
            $token = $this->tokenParser->parse($cookies[$cookieName]);
90
        } catch (\InvalidArgumentException $invalidToken) {
91
            return null;
92
        }
93
94
        if (!$token->validate(new ValidationData())) {
95
            return null;
96
        }
97
98
        return $token;
99
    }
100
101
    public function appendToken(
102
        SessionInterface $sessionContainer,
103
        ResponseInterface $response,
104
        Token $token = null
105
    ) : ResponseInterface
106
    {
107
        $sessionContainerChanged = $sessionContainer->hasChanged();
108
109
        if ($sessionContainerChanged && $sessionContainer->isEmpty()) {
110
            return FigResponseCookies::set($response, $this->getExpirationCookie());
111
        }
112
113
        if ($sessionContainerChanged || ($this->shouldTokenBeRefreshed($token) && !$sessionContainer->isEmpty())) {
114
            return FigResponseCookies::set($response, $this->getTokenCookie($sessionContainer));
115
        }
116
117
        return $response;
118
    }
119
120
    public function extractSessionContainer(Token $token = null) : SessionInterface
121
    {
122
        try {
123
            if (is_null($token) || !$token->verify($this->signer, $this->verificationKey)) {
124
                return new Session();
125
            }
126
127
            // Re-encode the payload and decode as array to not get stdClass tree
128
            return new Session(
129
                json_decode(json_encode($token->getClaim(self::SESSION_CLAIM, [])), true)
130
            );
131
        } catch (\BadMethodCallException $invalidToken) {
132
            return new Session();
133
        }
134
    }
135
136
    private function shouldTokenBeRefreshed(Token $token = null) : bool
137
    {
138
        if (is_null($token)) {
139
            return false;
140
        }
141
142
        if (!$token->hasClaim(self::ISSUED_AT_CLAIM)) {
143
            return false;
144
        }
145
146
        return time() >= ($token->getClaim(self::ISSUED_AT_CLAIM) + $this->refreshTime);
147
    }
148
149
    private function getTokenCookie(SessionInterface $sessionContainer) : SetCookie
150
    {
151
        $timestamp = time();
152
        return $this
153
            ->defaultCookie
154
            ->withValue(
155
                (new Builder())
156
                    ->setIssuedAt($timestamp)
157
                    ->setExpiration($timestamp + $this->expirationTime)
158
                    ->set(self::SESSION_CLAIM, $sessionContainer->toArray())
159
                    ->sign($this->signer, $this->signatureKey)
160
                    ->getToken()
161
            )
162
            ->withExpires($timestamp + $this->expirationTime);
163
    }
164
165
    private function getExpirationCookie() : SetCookie
166
    {
167
        $expirationDate = new \DateTime('-30 days');
168
        return $this
169
            ->defaultCookie
170
            ->withValue(null)
171
            ->withExpires($expirationDate->getTimestamp());
172
    }
173
}
174