Passed
Push — master ( 04447c...46f99c )
by Jelmer
02:58
created

JwtToPsrMapper::appendToken()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 7
cts 7
cp 1
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 11
nc 3
nop 3
crap 6
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 17
    public 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
    {
52 17
        $this->signer = $signer;
53 17
        $this->signatureKey = $signatureKey;
54 17
        $this->verificationKey = $verificationKey;
55 17
        $this->tokenParser = $tokenParser;
56 17
        $this->defaultCookie = clone $defaultCookie;
57 17
        $this->expirationTime = $expirationTime;
58 17
        $this->refreshTime = $refreshTime;
59 17
    }
60
61 1
    public static function fromAsymmetricKeyDefaults(
62
        string $privateRsaKey,
63
        string $publicRsaKey,
64
        int $expirationTime
65
    ) : JwtToPsrMapper
66
    {
67 1
        return new self(
68 1
            new Signer\Rsa\Sha256(),
69
            $privateRsaKey,
70
            $publicRsaKey,
71 1
            SetCookie::create(self::DEFAULT_COOKIE)
72 1
                ->withSecure(true)
73 1
                ->withHttpOnly(true)
74 1
                ->withPath('/'),
75 1
            new Parser(),
76
            $expirationTime
77
        );
78
    }
79
80
    /** @return  ?Token */
81 4
    public function parseToken(ServerRequestInterface $request)
82
    {
83 4
        $cookies = $request->getCookieParams();
84 4
        $cookieName = $this->defaultCookie->getName();
85
86 4
        if (!isset($cookies[$cookieName])) {
87 1
            return null;
88
        }
89
90
        try {
91 3
            $token = $this->tokenParser->parse($cookies[$cookieName]);
92 1
        } catch (\InvalidArgumentException $invalidToken) {
93 1
            return null;
94
        }
95
96 2
        if (!$token->validate(new ValidationData())) {
97 1
            return null;
98
        }
99
100 1
        return $token;
101
    }
102
103 8
    public function appendToken(
104
        SessionInterface $session,
105
        ResponseInterface $response,
106
        Token $token = null
107
    ) : ResponseInterface
108
    {
109 8
        $sessionContainerChanged = $session->hasChanged();
110
111 8
        if ($sessionContainerChanged && $session->isEmpty()) {
112 2
            return FigResponseCookies::set($response, $this->getExpirationCookie());
113
        }
114
115 6
        if ($sessionContainerChanged || ($this->shouldTokenBeRefreshed($token) && !$session->isEmpty())) {
116 3
            return FigResponseCookies::set($response, $this->getTokenCookie($session));
117
        }
118
119 3
        return $response;
120
    }
121
122 2
    private function getExpirationCookie() : SetCookie
123
    {
124 2
        $expirationDate = new \DateTime('-30 days');
125
        return $this
126 2
            ->defaultCookie
127 2
            ->withValue(null)
128 2
            ->withExpires($expirationDate->getTimestamp());
129
    }
130
131 4
    private function shouldTokenBeRefreshed(Token $token = null) : bool
132
    {
133 4
        if (is_null($token)) {
134 2
            return false;
135
        }
136
137 2
        if (!$token->hasClaim(self::ISSUED_AT_CLAIM)) {
138
            return false;
139
        }
140
141 2
        return time() >= ($token->getClaim(self::ISSUED_AT_CLAIM) + $this->refreshTime);
142
    }
143
144 3
    private function getTokenCookie(SessionInterface $session) : SetCookie
145
    {
146 3
        $timestamp = time();
147
        return $this
148 3
            ->defaultCookie
149 3
            ->withValue(
150 3
                (new Builder())
151 3
                    ->setIssuedAt($timestamp)
152 3
                    ->setExpiration($timestamp + $this->expirationTime)
153 3
                    ->set(self::SESSION_CLAIM, $session->toArray())
154 3
                    ->sign($this->signer, $this->signatureKey)
155 3
                    ->getToken()
156
            )
157 3
            ->withExpires($timestamp + $this->expirationTime);
158
    }
159
160 4
    public function extractSessionContainer(Token $token = null) : SessionInterface
161
    {
162
        try {
163 4
            if (is_null($token) || !$token->verify($this->signer, $this->verificationKey)) {
164 2
                return new Session();
165
            }
166
167
            // Re-encode the payload and decode as array to not get stdClass tree
168 1
            return new Session(
169 1
                json_decode(json_encode($token->getClaim(self::SESSION_CLAIM, [])), true)
170
            );
171 1
        } catch (\BadMethodCallException $invalidToken) {
172 1
            return new Session();
173
        }
174
    }
175
}
176