Passed
Push — master ( 61a29a...479ef7 )
by Alex
02:03
created

JWTGenerator::willExpireAt()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace AlexLisenkov\LaravelWebPush;
4
5
use AlexLisenkov\LaravelWebPush\Contracts\JWTGeneratorContract;
6
use Base64Url\Base64Url;
7
use Illuminate\Contracts\Config\Repository as ConfigRepository;
8
use Jose\Component\Core\AlgorithmManager;
9
use Jose\Component\Core\Converter\StandardConverter;
10
use Jose\Component\Core\JWK;
11
use Jose\Component\Signature\Algorithm\ES256;
12
use Jose\Component\Signature\JWS;
13
use Jose\Component\Signature\JWSBuilder;
14
use Jose\Component\Signature\Serializer\CompactSerializer;
15
16
class JWTGenerator implements JWTGeneratorContract
17
{
18
    /**
19
     * @var int
20
     */
21
    private $expires_at;
22
    /**
23
     * @var string
24
     */
25
    private $audience;
26
    /**
27
     * @var ConfigRepository
28
     */
29
    private $config_repository;
30
31 16
    public function __construct(ConfigRepository $config_repository)
32
    {
33 16
        $this->config_repository = $config_repository;
34 16
    }
35
36
    /**
37
     * @param string $aud
38
     *
39
     * @return JWTGeneratorContract
40
     */
41 7
    public function withAudience(string $aud): JWTGeneratorContract
42
    {
43 7
        $this->audience = $aud;
44
45 7
        return $this;
46
    }
47
48
    /**
49
     * @param int $time
50
     *
51
     * @return JWTGeneratorContract
52
     */
53 1
    public function willExpireAt(int $time): JWTGeneratorContract
54
    {
55 1
        $this->expires_at = $time;
56
57 1
        return $this;
58
    }
59
60
    /**
61
     * @return string
62
     * @throws \Exception
63
     */
64 1
    public function serialize(): string
65
    {
66 1
        $jsonConverter = new StandardConverter();
67 1
        $jwsCompactSerializer = new CompactSerializer($jsonConverter);
68
69 1
        return $jwsCompactSerializer->serialize($this->getJWS(), 0);
70
    }
71
72
    /**
73
     * @return JWS
74
     * @throws \Exception
75
     */
76 3
    public function getJWS(): JWS
77
    {
78 3
        $jsonConverter = new StandardConverter();
79 3
        $jwsBuilder = new JWSBuilder($jsonConverter, AlgorithmManager::create([new ES256()]));
80
81
        return $jwsBuilder
82 3
            ->create()
83 3
            ->withPayload($this->getPayload())
84 3
            ->addSignature($this->getJWK(), $this->getHeader())
85 3
            ->build();
86
    }
87
88
    /**
89
     * @return string
90
     * @throws \Exception
91
     */
92 8
    public function getPayload(): string
93
    {
94 8
        if (!$this->audience) {
95 1
            throw new \Exception('No audience set');
96
        }
97
98 7
        if (!$this->getExpiresAt()) {
99 1
            $this->willExpireIn($this->getConfigVariable('expiration', Constants::DEFAULT_EXPIRE));
100
        }
101
102 7
        return json_encode([
103 7
            'aud' => $this->audience,
104 7
            'exp' => $this->getExpiresAt(),
105 7
            'sub' => $this->getConfigVariable('subject', env('APP_URL', 'mailto:[email protected]')),
106 7
        ], JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
107
    }
108
109
    /**
110
     * @param int $time
111
     *
112
     * @return JWTGeneratorContract
113
     */
114 7
    public function willExpireIn(int $time): JWTGeneratorContract
115
    {
116 7
        $this->expires_at = time() + $time;
117
118 7
        return $this;
119
    }
120
121
    /**
122
     * @param string $key
123
     *
124
     * @param $default
125
     *
126
     * @return mixed
127
     */
128 13
    private function getConfigVariable(string $key, $default = null)
129
    {
130 13
        return $this->config_repository->get(Constants::CONFIG_KEY . '.' . $key, $default);
131
    }
132
133
    /**
134
     * @return JWK
135
     */
136 9
    public function getJWK(): JWK
137
    {
138 9
        $local_server_key = Base64Url::decode($this->getConfigVariable('public_key'));
139 9
        $public = $this->unserializePublicKey($local_server_key);
140
141 8
        return JWK::create([
142 8
            'kty' => 'EC',
143 8
            'crv' => 'P-256',
144 8
            'x' => Base64Url::encode(hex2bin($public['x'])),
145 8
            'y' => Base64Url::encode(hex2bin($public['y'])),
146 8
            'd' => $this->getConfigVariable('private_key'),
147
        ]);
148
    }
149
150
    /**
151
     * @param string $data
152
     *
153
     * @return array
154
     */
155 9
    private function unserializePublicKey(string $data): array
156
    {
157 9
        $data = bin2hex($data);
158 9
        if (mb_substr($data, 0, 2, '8bit') !== '04') {
159 1
            throw new \InvalidArgumentException('Invalid data: only uncompressed keys are supported.');
160
        }
161 8
        $data = mb_substr($data, 2, null, '8bit');
162 8
        $dataLength = mb_strlen($data, '8bit');
163
164
        return [
165 8
            'x' => mb_substr($data, 0, $dataLength / 2, '8bit'),
166 8
            'y' => mb_substr($data, $dataLength / 2, null, '8bit'),
167
        ];
168
    }
169
170
    /**
171
     * @return array
172
     */
173 4
    public function getHeader(): array
174
    {
175
        return [
176 4
            'typ' => 'JWT',
177
            'alg' => 'ES256',
178
        ];
179
    }
180
181
    /**
182
     * Get ExpiresAt
183
     *
184
     * @return int|null
185
     */
186 8
    public function getExpiresAt(): ?int
187
    {
188 8
        return $this->expires_at;
189
    }
190
}
191