Passed
Push — master ( bf6fa9...817f88 )
by Nikolaos
06:58
created

Builder::getToken()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 13
nc 2
nop 0
dl 0
loc 20
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Phalcon\Security\JWT;
15
16
use Phalcon\Collection;
17
use Phalcon\Helper\Base64;
18
use Phalcon\Helper\Json;
19
use Phalcon\Security\JWT\Exceptions\ValidatorException;
20
use Phalcon\Security\JWT\Signer\SignerInterface;
21
use Phalcon\Security\JWT\Token\Enum;
22
use Phalcon\Security\JWT\Token\Item;
23
use Phalcon\Security\JWT\Token\Signature;
24
use Phalcon\Security\JWT\Token\Token;
25
26
use function is_array;
27
use function is_string;
28
use function preg_match;
29
use function time;
30
31
/**
32
 * Class Builder
33
 *
34
 * @property Collection      $claims
35
 * @property Collection      $jose
36
 * @property string          $passphrase
37
 * @property SignerInterface $signer
38
 *
39
 * @link https://tools.ietf.org/html/rfc7519
40
 */
41
class Builder
42
{
43
    /**
44
     * @var Collection
45
     */
46
    private $claims;
47
48
    /**
49
     * @var Collection
50
     */
51
    private $jose;
52
53
    /**
54
     * @var string
55
     */
56
    private $passphrase;
57
58
    /**
59
     * @var SignerInterface
60
     */
61
    private $signer;
62
63
    /**
64
     * Builder constructor.
65
     *
66
     * @param SignerInterface $signer
67
     */
68
    public function __construct(
69
        SignerInterface $signer
70
    ) {
71
        $this->init();
72
        $this->signer = $signer;
73
        $this->jose->set(
74
            Enum::ALGO,
75
            $this->signer->getAlgHeader()
76
        );
77
    }
78
79
    /**
80
     * @return Builder
81
     */
82
    public function init(): Builder
83
    {
84
        $this->passphrase = "";
85
        $this->claims     = new Collection();
86
        $this->jose       = new Collection(
87
            [
88
                Enum::TYPE => "JWT",
89
                Enum::ALGO => "none",
90
            ]
91
        );
92
93
        return $this;
94
    }
95
96
    /**
97
     * @return array|string
98
     */
99
    public function getAudience()
100
    {
101
        return $this->claims->get(Enum::AUDIENCE);
102
    }
103
104
    /**
105
     * @return array
106
     */
107
    public function getClaims(): array
108
    {
109
        return $this->claims->toArray();
110
    }
111
112
    /**
113
     * @return string|null
114
     */
115
    public function getContentType(): ?string
116
    {
117
        return $this->jose->get(Enum::CONTENT_TYPE, null, "string");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->jose->get(Phalcon...T_TYPE, null, 'string') targeting Phalcon\Collection::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
118
    }
119
120
    /**
121
     * @return int|null
122
     */
123
    public function getExpirationTime(): ?int
124
    {
125
        return $this->claims->get(Enum::EXPIRATION_TIME, null, "int");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->claims->get(Phalc...TION_TIME, null, 'int') targeting Phalcon\Collection::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
126
    }
127
128
    /**
129
     * @return array
130
     */
131
    public function getHeaders(): array
132
    {
133
        return $this->jose->toArray();
134
    }
135
136
    /**
137
     * @return string|null
138
     */
139
    public function getId(): ?string
140
    {
141
        return $this->claims->get(Enum::ID, null, "string");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->claims->get(Phalc...um::ID, null, 'string') targeting Phalcon\Collection::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
142
    }
143
144
    /**
145
     * @return int|null
146
     */
147
    public function getIssuedAt(): ?int
148
    {
149
        return $this->claims->get(Enum::ISSUED_AT, null, "int");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->claims->get(Phalc...ISSUED_AT, null, 'int') targeting Phalcon\Collection::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
150
    }
151
152
    /**
153
     * @return string|null
154
     */
155
    public function getIssuer(): ?string
156
    {
157
        return $this->claims->get(Enum::ISSUER, null, "string");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->claims->get(Phalc...ISSUER, null, 'string') targeting Phalcon\Collection::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
158
    }
159
160
    /**
161
     * @return int|null
162
     */
163
    public function getNotBefore(): ?int
164
    {
165
        return $this->claims->get(Enum::NOT_BEFORE, null, "int");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->claims->get(Phalc...OT_BEFORE, null, 'int') targeting Phalcon\Collection::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
166
    }
167
168
    /**
169
     * @return string|null
170
     */
171
    public function getSubject(): ?string
172
    {
173
        return $this->claims->get(Enum::SUBJECT, null, "string");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->claims->get(Phalc...UBJECT, null, 'string') targeting Phalcon\Collection::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
174
    }
175
176
    /**
177
     * @return Token
178
     * @throws ValidatorException
179
     */
180
    public function getToken(): Token
181
    {
182
        if (empty($this->passphrase)) {
183
            throw new ValidatorException(
184
                "Invalid passphrase (empty)"
185
            );
186
        }
187
188
        $encodedClaims    = Base64::encodeUrl(Json::encode($this->getClaims()));
189
        $claims           = new Item($this->getClaims(), $encodedClaims);
190
        $encodedHeaders   = Base64::encodeUrl(Json::encode($this->getHeaders()));
191
        $headers          = new Item($this->getHeaders(), $encodedHeaders);
192
        $signatureHash    = $this->signer->sign(
193
            $encodedHeaders . "." . $encodedClaims,
194
            $this->passphrase
195
        );
196
        $encodedSignature = Base64::encodeUrl($signatureHash);
197
        $signature        = new Signature($signatureHash, $encodedSignature);
198
199
        return new Token($headers, $claims, $signature);
200
    }
201
202
    /**
203
     * @return string
204
     */
205
    public function getPassphrase(): string
206
    {
207
        return $this->passphrase;
208
    }
209
210
    /**
211
     * The "aud" (audience) claim identifies the recipients that the JWT is
212
     * intended for.  Each principal intended to process the JWT MUST
213
     * identify itself with a value in the audience claim.  If the principal
214
     * processing the claim does not identify itself with a value in the
215
     * "aud" claim when this claim is present, then the JWT MUST be
216
     * rejected.  In the general case, the "aud" value is an array of case-
217
     * sensitive strings, each containing a StringOrURI value.  In the
218
     * special case when the JWT has one audience, the "aud" value MAY be a
219
     * single case-sensitive string containing a StringOrURI value.  The
220
     * interpretation of audience values is generally application specific.
221
     * Use of this claim is OPTIONAL.
222
     *
223
     * @param mixed $audience
224
     *
225
     * @return Builder
226
     * @throws ValidatorException
227
     */
228
    public function setAudience($audience): Builder
229
    {
230
        if (!is_string($audience) && !is_array($audience)) {
231
            throw new ValidatorException(
232
                "Invalid Audience"
233
            );
234
        }
235
236
        if (is_string($audience)) {
237
            $audience = [$audience];
238
        }
239
240
        return $this->setClaim(Enum::AUDIENCE, $audience);
241
    }
242
243
    /**
244
     * Sets the content type header 'cty'
245
     *
246
     * @param string $contentType
247
     *
248
     * @return Builder
249
     */
250
    public function setContentType(string $contentType): Builder
251
    {
252
        $this->jose->set(Enum::CONTENT_TYPE, $contentType);
253
254
        return $this;
255
    }
256
257
    /**
258
     * The "exp" (expiration time) claim identifies the expiration time on
259
     * or after which the JWT MUST NOT be accepted for processing.  The
260
     * processing of the "exp" claim requires that the current date/time
261
     * MUST be before the expiration date/time listed in the "exp" claim.
262
     * Implementers MAY provide for some small leeway, usually no more than
263
     * a few minutes, to account for clock skew.  Its value MUST be a number
264
     * containing a NumericDate value.  Use of this claim is OPTIONAL.
265
     *
266
     * @param int $timestamp
267
     *
268
     * @return Builder
269
     * @throws ValidatorException
270
     */
271
    public function setExpirationTime(int $timestamp): Builder
272
    {
273
        if ($timestamp < time()) {
274
            throw new ValidatorException(
275
                "Invalid Expiration Time"
276
            );
277
        }
278
279
        return $this->setClaim(Enum::EXPIRATION_TIME, $timestamp);
280
    }
281
282
    /**
283
     * The "jti" (JWT ID) claim provides a unique identifier for the JWT.
284
     * The identifier value MUST be assigned in a manner that ensures that
285
     * there is a negligible probability that the same value will be
286
     * accidentally assigned to a different data object; if the application
287
     * uses multiple issuers, collisions MUST be prevented among values
288
     * produced by different issuers as well.  The "jti" claim can be used
289
     * to prevent the JWT from being replayed.  The "jti" value is a case-
290
     * sensitive string.  Use of this claim is OPTIONAL.
291
     *
292
     * @param string $id
293
     *
294
     * @return Builder
295
     */
296
    public function setId(string $id): Builder
297
    {
298
        return $this->setClaim(Enum::ID, $id);
299
    }
300
301
    /**
302
     * The "iat" (issued at) claim identifies the time at which the JWT was
303
     * issued.  This claim can be used to determine the age of the JWT.  Its
304
     * value MUST be a number containing a NumericDate value.  Use of this
305
     * claim is OPTIONAL.
306
     *
307
     * @param int $timestamp
308
     *
309
     * @return Builder
310
     */
311
    public function setIssuedAt(int $timestamp): Builder
312
    {
313
        return $this->setClaim(Enum::ISSUED_AT, $timestamp);
314
    }
315
316
    /**
317
     * The "iss" (issuer) claim identifies the principal that issued the
318
     * JWT.  The processing of this claim is generally application specific.
319
     * The "iss" value is a case-sensitive string containing a StringOrURI
320
     * value.  Use of this claim is OPTIONAL.
321
     *
322
     * @param string $issuer
323
     *
324
     * @return Builder
325
     */
326
    public function setIssuer(string $issuer): Builder
327
    {
328
        return $this->setClaim(Enum::ISSUER, $issuer);
329
    }
330
331
    /**
332
     * The "nbf" (not before) claim identifies the time before which the JWT
333
     * MUST NOT be accepted for processing.  The processing of the "nbf"
334
     * claim requires that the current date/time MUST be after or equal to
335
     * the not-before date/time listed in the "nbf" claim.  Implementers MAY
336
     * provide for some small leeway, usually no more than a few minutes, to
337
     * account for clock skew.  Its value MUST be a number containing a
338
     * NumericDate value.  Use of this claim is OPTIONAL.
339
     *
340
     * @param int $timestamp
341
     *
342
     * @return Builder
343
     * @throws ValidatorException
344
     */
345
    public function setNotBefore(int $timestamp): Builder
346
    {
347
        if ($timestamp > time()) {
348
            throw new ValidatorException(
349
                "Invalid Not Before"
350
            );
351
        }
352
353
        return $this->setClaim(Enum::NOT_BEFORE, $timestamp);
354
    }
355
356
    /**
357
     * The "sub" (subject) claim identifies the principal that is the
358
     * subject of the JWT.  The claims in a JWT are normally statements
359
     * about the subject.  The subject value MUST either be scoped to be
360
     * locally unique in the context of the issuer or be globally unique.
361
     * The processing of this claim is generally application specific.  The
362
     * "sub" value is a case-sensitive string containing a StringOrURI
363
     * value.  Use of this claim is OPTIONAL.
364
     *
365
     * @param string $subject
366
     *
367
     * @return Builder
368
     */
369
    public function setSubject(string $subject): Builder
370
    {
371
        return $this->setClaim(Enum::SUBJECT, $subject);
372
    }
373
374
    /**
375
     * @param string $passphrase
376
     *
377
     * @return Builder
378
     * @throws ValidatorException
379
     */
380
    public function setPassphrase(string $passphrase): Builder
381
    {
382
        if (
383
            !preg_match(
384
                "/^.*(?=.{12,}+)(?=.*[0-9]+)(?=.*[A-Z]+)(?=.*[a-z]+)(?=.*[*&!@%^#\$]+).*$/",
385
                $passphrase
386
            )
387
        ) {
388
            throw new ValidatorException(
389
                "Invalid passphrase (too weak)"
390
            );
391
        }
392
393
        $this->passphrase = $passphrase;
394
395
        return $this;
396
    }
397
398
    /**
399
     * Sets a registered claim
400
     *
401
     * @param string $name
402
     * @param mixed  $value
403
     *
404
     * @return Builder
405
     */
406
    private function setClaim(string $name, $value): Builder
407
    {
408
        $this->claims->set($name, $value);
409
410
        return $this;
411
    }
412
}
413