JWTGenerator::getPayload()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

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