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 | 42 | public function __construct( |
|
69 | SignerInterface $signer |
||
70 | ) { |
||
71 | 42 | $this->init(); |
|
72 | 42 | $this->signer = $signer; |
|
73 | 42 | $this->jose->set( |
|
74 | 42 | Enum::ALGO, |
|
75 | 42 | $this->signer->getAlgHeader() |
|
76 | ); |
||
77 | 42 | } |
|
78 | |||
79 | /** |
||
80 | * @return Builder |
||
81 | */ |
||
82 | 42 | public function init(): Builder |
|
83 | { |
||
84 | 42 | $this->passphrase = ""; |
|
85 | 42 | $this->claims = new Collection(); |
|
86 | 42 | $this->jose = new Collection( |
|
87 | [ |
||
88 | 42 | Enum::TYPE => "JWT", |
|
89 | 42 | Enum::ALGO => "none", |
|
90 | ] |
||
91 | ); |
||
92 | |||
93 | 42 | return $this; |
|
94 | } |
||
95 | |||
96 | /** |
||
97 | * @return array|string |
||
98 | */ |
||
99 | 1 | public function getAudience() |
|
100 | { |
||
101 | 1 | return $this->claims->get(Enum::AUDIENCE); |
|
102 | } |
||
103 | |||
104 | /** |
||
105 | * @return array |
||
106 | */ |
||
107 | 25 | public function getClaims(): array |
|
108 | { |
||
109 | 25 | return $this->claims->toArray(); |
|
110 | } |
||
111 | |||
112 | /** |
||
113 | * @return string|null |
||
114 | */ |
||
115 | 1 | public function getContentType(): ?string |
|
116 | { |
||
117 | /** @var string $result */ |
||
118 | 1 | $result = $this->jose->get( |
|
0 ignored issues
–
show
|
|||
119 | 1 | Enum::CONTENT_TYPE, |
|
120 | 1 | null, |
|
121 | 1 | "string" |
|
122 | ); |
||
123 | |||
124 | 1 | return $result; |
|
125 | } |
||
126 | |||
127 | /** |
||
128 | * @return int|null |
||
129 | */ |
||
130 | 1 | public function getExpirationTime(): ?int |
|
131 | { |
||
132 | /** @var int|null $result */ |
||
133 | 1 | $result = $this->claims->get( |
|
0 ignored issues
–
show
Are you sure the assignment to
$result is correct as $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 assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
134 | 1 | Enum::EXPIRATION_TIME, |
|
135 | 1 | null, |
|
136 | 1 | "int" |
|
137 | ); |
||
138 | |||
139 | 1 | return $result; |
|
140 | } |
||
141 | |||
142 | /** |
||
143 | * @return array |
||
144 | */ |
||
145 | 25 | public function getHeaders(): array |
|
146 | { |
||
147 | 25 | return $this->jose->toArray(); |
|
148 | } |
||
149 | |||
150 | /** |
||
151 | * @return string|null |
||
152 | */ |
||
153 | 1 | public function getId(): ?string |
|
154 | { |
||
155 | /** @var string $result */ |
||
156 | 1 | $result = $this->claims->get( |
|
0 ignored issues
–
show
Are you sure the assignment to
$result is correct as $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 assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
157 | 1 | Enum::ID, |
|
158 | 1 | null, |
|
159 | 1 | "string" |
|
160 | ); |
||
161 | |||
162 | 1 | return $result; |
|
163 | } |
||
164 | |||
165 | /** |
||
166 | * @return int|null |
||
167 | */ |
||
168 | 1 | public function getIssuedAt(): ?int |
|
169 | { |
||
170 | /** @var int|null $result */ |
||
171 | 1 | $result = $this->claims->get( |
|
0 ignored issues
–
show
Are you sure the assignment to
$result is correct as $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 assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
172 | 1 | Enum::ISSUED_AT, |
|
173 | 1 | null, |
|
174 | 1 | "int" |
|
175 | ); |
||
176 | |||
177 | 1 | return $result; |
|
178 | } |
||
179 | |||
180 | /** |
||
181 | * @return string|null |
||
182 | */ |
||
183 | 1 | public function getIssuer(): ?string |
|
184 | { |
||
185 | /** @var string|null $result */ |
||
186 | 1 | $result = $this->claims->get( |
|
0 ignored issues
–
show
Are you sure the assignment to
$result is correct as $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 assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
187 | 1 | Enum::ISSUER, |
|
188 | 1 | null, |
|
189 | 1 | "string" |
|
190 | ); |
||
191 | |||
192 | 1 | return $result; |
|
193 | } |
||
194 | |||
195 | /** |
||
196 | * @return int|null |
||
197 | */ |
||
198 | 1 | public function getNotBefore(): ?int |
|
199 | { |
||
200 | /** @var int|null $result */ |
||
201 | 1 | $result = $this->claims->get( |
|
0 ignored issues
–
show
Are you sure the assignment to
$result is correct as $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 assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
202 | 1 | Enum::NOT_BEFORE, |
|
203 | 1 | null, |
|
204 | 1 | "int" |
|
205 | ); |
||
206 | |||
207 | 1 | return $result; |
|
208 | } |
||
209 | |||
210 | /** |
||
211 | * @return string|null |
||
212 | */ |
||
213 | 2 | public function getSubject(): ?string |
|
214 | { |
||
215 | /** @var string|null $result */ |
||
216 | 2 | $result = $this->claims->get( |
|
0 ignored issues
–
show
Are you sure the assignment to
$result is correct as $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 assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
217 | 2 | Enum::SUBJECT, |
|
218 | 2 | null, |
|
219 | 2 | "string" |
|
220 | ); |
||
221 | |||
222 | 2 | return $result; |
|
223 | } |
||
224 | |||
225 | /** |
||
226 | * @return Token |
||
227 | * @throws ValidatorException |
||
228 | */ |
||
229 | 25 | public function getToken(): Token |
|
230 | { |
||
231 | 25 | if (empty($this->passphrase)) { |
|
232 | 1 | throw new ValidatorException( |
|
233 | 1 | "Invalid passphrase (empty)" |
|
234 | ); |
||
235 | } |
||
236 | |||
237 | 24 | $encodedClaims = Base64::encodeUrl(Json::encode($this->getClaims())); |
|
238 | 24 | $claims = new Item($this->getClaims(), $encodedClaims); |
|
239 | 24 | $encodedHeaders = Base64::encodeUrl(Json::encode($this->getHeaders())); |
|
240 | 24 | $headers = new Item($this->getHeaders(), $encodedHeaders); |
|
241 | 24 | $signatureHash = $this->signer->sign( |
|
242 | 24 | $encodedHeaders . "." . $encodedClaims, |
|
243 | 24 | $this->passphrase |
|
244 | ); |
||
245 | 24 | $encodedSignature = Base64::encodeUrl($signatureHash); |
|
246 | 24 | $signature = new Signature($signatureHash, $encodedSignature); |
|
247 | |||
248 | 24 | return new Token($headers, $claims, $signature); |
|
249 | } |
||
250 | |||
251 | /** |
||
252 | * @return string |
||
253 | */ |
||
254 | 1 | public function getPassphrase(): string |
|
255 | { |
||
256 | 1 | return $this->passphrase; |
|
257 | } |
||
258 | |||
259 | /** |
||
260 | * The "aud" (audience) claim identifies the recipients that the JWT is |
||
261 | * intended for. Each principal intended to process the JWT MUST |
||
262 | * identify itself with a value in the audience claim. If the principal |
||
263 | * processing the claim does not identify itself with a value in the |
||
264 | * "aud" claim when this claim is present, then the JWT MUST be |
||
265 | * rejected. In the general case, the "aud" value is an array of case- |
||
266 | * sensitive strings, each containing a StringOrURI value. In the |
||
267 | * special case when the JWT has one audience, the "aud" value MAY be a |
||
268 | * single case-sensitive string containing a StringOrURI value. The |
||
269 | * interpretation of audience values is generally application specific. |
||
270 | * Use of this claim is OPTIONAL. |
||
271 | * |
||
272 | * @param mixed $audience |
||
273 | * |
||
274 | * @return Builder |
||
275 | * @throws ValidatorException |
||
276 | */ |
||
277 | 26 | public function setAudience($audience): Builder |
|
278 | { |
||
279 | 26 | if (!is_string($audience) && !is_array($audience)) { |
|
280 | 1 | throw new ValidatorException( |
|
281 | 1 | "Invalid Audience" |
|
282 | ); |
||
283 | } |
||
284 | |||
285 | 25 | if (is_string($audience)) { |
|
286 | 25 | $audience = [$audience]; |
|
287 | } |
||
288 | |||
289 | 25 | return $this->setClaim(Enum::AUDIENCE, $audience); |
|
290 | } |
||
291 | |||
292 | /** |
||
293 | * Sets the content type header 'cty' |
||
294 | * |
||
295 | * @param string $contentType |
||
296 | * |
||
297 | * @return Builder |
||
298 | */ |
||
299 | 1 | public function setContentType(string $contentType): Builder |
|
300 | { |
||
301 | 1 | $this->jose->set(Enum::CONTENT_TYPE, $contentType); |
|
302 | |||
303 | 1 | return $this; |
|
304 | } |
||
305 | |||
306 | /** |
||
307 | * The "exp" (expiration time) claim identifies the expiration time on |
||
308 | * or after which the JWT MUST NOT be accepted for processing. The |
||
309 | * processing of the "exp" claim requires that the current date/time |
||
310 | * MUST be before the expiration date/time listed in the "exp" claim. |
||
311 | * Implementers MAY provide for some small leeway, usually no more than |
||
312 | * a few minutes, to account for clock skew. Its value MUST be a number |
||
313 | * containing a NumericDate value. Use of this claim is OPTIONAL. |
||
314 | * |
||
315 | * @param int $timestamp |
||
316 | * |
||
317 | * @return Builder |
||
318 | * @throws ValidatorException |
||
319 | */ |
||
320 | 26 | public function setExpirationTime(int $timestamp): Builder |
|
321 | { |
||
322 | 26 | if ($timestamp < time()) { |
|
323 | 1 | throw new ValidatorException( |
|
324 | 1 | "Invalid Expiration Time" |
|
325 | ); |
||
326 | } |
||
327 | |||
328 | 25 | return $this->setClaim(Enum::EXPIRATION_TIME, $timestamp); |
|
329 | } |
||
330 | |||
331 | /** |
||
332 | * The "jti" (JWT ID) claim provides a unique identifier for the JWT. |
||
333 | * The identifier value MUST be assigned in a manner that ensures that |
||
334 | * there is a negligible probability that the same value will be |
||
335 | * accidentally assigned to a different data object; if the application |
||
336 | * uses multiple issuers, collisions MUST be prevented among values |
||
337 | * produced by different issuers as well. The "jti" claim can be used |
||
338 | * to prevent the JWT from being replayed. The "jti" value is a case- |
||
339 | * sensitive string. Use of this claim is OPTIONAL. |
||
340 | * |
||
341 | * @param string $id |
||
342 | * |
||
343 | * @return Builder |
||
344 | */ |
||
345 | 25 | public function setId(string $id): Builder |
|
346 | { |
||
347 | 25 | return $this->setClaim(Enum::ID, $id); |
|
348 | } |
||
349 | |||
350 | /** |
||
351 | * The "iat" (issued at) claim identifies the time at which the JWT was |
||
352 | * issued. This claim can be used to determine the age of the JWT. Its |
||
353 | * value MUST be a number containing a NumericDate value. Use of this |
||
354 | * claim is OPTIONAL. |
||
355 | * |
||
356 | * @param int $timestamp |
||
357 | * |
||
358 | * @return Builder |
||
359 | */ |
||
360 | 25 | public function setIssuedAt(int $timestamp): Builder |
|
361 | { |
||
362 | 25 | return $this->setClaim(Enum::ISSUED_AT, $timestamp); |
|
363 | } |
||
364 | |||
365 | /** |
||
366 | * The "iss" (issuer) claim identifies the principal that issued the |
||
367 | * JWT. The processing of this claim is generally application specific. |
||
368 | * The "iss" value is a case-sensitive string containing a StringOrURI |
||
369 | * value. Use of this claim is OPTIONAL. |
||
370 | * |
||
371 | * @param string $issuer |
||
372 | * |
||
373 | * @return Builder |
||
374 | */ |
||
375 | 25 | public function setIssuer(string $issuer): Builder |
|
376 | { |
||
377 | 25 | return $this->setClaim(Enum::ISSUER, $issuer); |
|
378 | } |
||
379 | |||
380 | /** |
||
381 | * The "nbf" (not before) claim identifies the time before which the JWT |
||
382 | * MUST NOT be accepted for processing. The processing of the "nbf" |
||
383 | * claim requires that the current date/time MUST be after or equal to |
||
384 | * the not-before date/time listed in the "nbf" claim. Implementers MAY |
||
385 | * provide for some small leeway, usually no more than a few minutes, to |
||
386 | * account for clock skew. Its value MUST be a number containing a |
||
387 | * NumericDate value. Use of this claim is OPTIONAL. |
||
388 | * |
||
389 | * @param int $timestamp |
||
390 | * |
||
391 | * @return Builder |
||
392 | * @throws ValidatorException |
||
393 | */ |
||
394 | 26 | public function setNotBefore(int $timestamp): Builder |
|
395 | { |
||
396 | 26 | if ($timestamp > time()) { |
|
397 | 1 | throw new ValidatorException( |
|
398 | 1 | "Invalid Not Before" |
|
399 | ); |
||
400 | } |
||
401 | |||
402 | 25 | return $this->setClaim(Enum::NOT_BEFORE, $timestamp); |
|
403 | } |
||
404 | |||
405 | /** |
||
406 | * The "sub" (subject) claim identifies the principal that is the |
||
407 | * subject of the JWT. The claims in a JWT are normally statements |
||
408 | * about the subject. The subject value MUST either be scoped to be |
||
409 | * locally unique in the context of the issuer or be globally unique. |
||
410 | * The processing of this claim is generally application specific. The |
||
411 | * "sub" value is a case-sensitive string containing a StringOrURI |
||
412 | * value. Use of this claim is OPTIONAL. |
||
413 | * |
||
414 | * @param string $subject |
||
415 | * |
||
416 | * @return Builder |
||
417 | */ |
||
418 | 26 | public function setSubject(string $subject): Builder |
|
419 | { |
||
420 | 26 | return $this->setClaim(Enum::SUBJECT, $subject); |
|
421 | } |
||
422 | |||
423 | /** |
||
424 | * @param string $passphrase |
||
425 | * |
||
426 | * @return Builder |
||
427 | * @throws ValidatorException |
||
428 | */ |
||
429 | 26 | public function setPassphrase(string $passphrase): Builder |
|
430 | { |
||
431 | if ( |
||
432 | 26 | !preg_match( |
|
433 | 26 | "/^.*(?=.{12,}+)(?=.*[0-9]+)(?=.*[A-Z]+)(?=.*[a-z]+)(?=.*[*&!@%^#\$]+).*$/", |
|
434 | 26 | $passphrase |
|
435 | ) |
||
436 | ) { |
||
437 | 1 | throw new ValidatorException( |
|
438 | 1 | "Invalid passphrase (too weak)" |
|
439 | ); |
||
440 | } |
||
441 | |||
442 | 25 | $this->passphrase = $passphrase; |
|
443 | |||
444 | 25 | return $this; |
|
445 | } |
||
446 | |||
447 | /** |
||
448 | * Sets a registered claim |
||
449 | * |
||
450 | * @param string $name |
||
451 | * @param mixed $value |
||
452 | * |
||
453 | * @return Builder |
||
454 | */ |
||
455 | 32 | private function setClaim(string $name, $value): Builder |
|
456 | { |
||
457 | 32 | $this->claims->set($name, $value); |
|
458 | |||
459 | 32 | return $this; |
|
460 | } |
||
461 | } |
||
462 |
This check looks for function or method calls that always return null and whose return value is assigned to a variable.
The method
getObject()
can return nothing but null, so it makes no sense to assign that value to a variable.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.