Skip to content

Commit 1266a4a

Browse files
committed
Add Firebase JWT v6 compatibility
1 parent 9926bee commit 1266a4a

File tree

8 files changed

+234
-19
lines changed

8 files changed

+234
-19
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@ Package is optimized for invoking in very early lifecycle phase of your App
2020
## Requirements
2121
Package requires:
2222

23-
- PHP version at least 7.4
23+
- PHP version 7.4, 8.0 or 8.1
2424

2525
Enabler requires:
2626

2727
- Temporary directory with writable access
2828

29+
SignUrl plugin requires:
30+
31+
- [Firebase JWT](https://github.com/firebase/php-jwt) v5 or v6
32+
2933
## Installation
3034
```shell
3135
composer require redbitcz/debug-mode-enabler

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"nette/utils": "^3.0"
2121
},
2222
"require-dev": {
23-
"firebase/php-jwt": "^5.0",
23+
"firebase/php-jwt": "^5.0||^6.0",
2424
"nette/tester": "2.4.2",
2525
"phpstan/phpstan": "1.8.2"
2626
},
@@ -38,7 +38,7 @@
3838
}
3939
},
4040
"scripts": {
41-
"phpstan": "phpstan analyze src -c phpstan.neon --level 8",
41+
"phpstan": "phpstan analyze -c phpstan.neon --level 5",
4242
"tester": "tester tests"
4343
}
4444
}

phpstan.neon

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
parameters:
2+
paths:
3+
- src
4+
excludePaths:
5+
analyse:
6+
- src/Plugin/JWT/*
7+
28
ignoreErrors:
39
-
410
message: '#Parameter \#3 \$options of function setcookie expects .+#'
511
path: src/Enabler.php
612
count: 2
13+
- '#.+ has unknown class (OpenSSLAsymmetricKey|OpenSSLCertificate) as its type.#' # PHP 7 compatibility
14+
- '#.+ has invalid type (OpenSSLAsymmetricKey|OpenSSLCertificate).#' # PHP 7 compatibility
15+
16+
reportUnmatchedIgnoredErrors: false

src/Plugin/JWT/JWTFirebaseV5.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/**
4+
* The MIT License (MIT)
5+
* Copyright (c) 2022 Redbit s.r.o., Jakub Bouček
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Redbitcz\DebugMode\Plugin\JWT;
11+
12+
use Firebase\JWT\JWT;
13+
use ReflectionMethod;
14+
use stdClass;
15+
16+
class JWTFirebaseV5 implements JWTImpl
17+
{
18+
public static function isAvailable(): bool
19+
{
20+
if (class_exists(JWT::class) === false) {
21+
return false;
22+
}
23+
24+
$params = (new ReflectionMethod(JWT::class, 'decode'))->getParameters();
25+
26+
// JWT v5 has second parameter named `$key`
27+
if ($params[1]->getName() === 'key') {
28+
return true;
29+
}
30+
31+
// JWT v5.5.0 already second parameter named `$keyOrKeyArray`, detect by third param (future compatibility)
32+
return $params[1]->getName() === 'keyOrKeyArray'
33+
&& isset($params[2])
34+
&& $params[2]->getName() === 'allowed_algs';
35+
}
36+
37+
public function decode(string $jwt, $key, string $alg): stdClass
38+
{
39+
return JWT::decode($jwt, $key, [$alg]);
40+
}
41+
42+
public function encode(array $payload, $key, string $alg): string
43+
{
44+
return JWT::encode($payload, $key, $alg);
45+
}
46+
47+
public function setTimestamp(?int $timestamp): void
48+
{
49+
JWT::$timestamp = $timestamp;
50+
}
51+
}

src/Plugin/JWT/JWTFirebaseV6.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/**
4+
* The MIT License (MIT)
5+
* Copyright (c) 2022 Redbit s.r.o., Jakub Bouček
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Redbitcz\DebugMode\Plugin\JWT;
11+
12+
use Firebase\JWT\JWT;
13+
use Firebase\JWT\Key;
14+
use ReflectionMethod;
15+
use stdClass;
16+
17+
class JWTFirebaseV6 extends JWTFirebaseV5
18+
{
19+
public static function isAvailable(): bool
20+
{
21+
if (class_exists(JWT::class) === false) {
22+
return false;
23+
}
24+
25+
$params = (new ReflectionMethod(JWT::class, 'decode'))->getParameters();
26+
27+
// JWT v6 has always second parameter named `$keyOrKeyArray`
28+
if ($params[1]->getName() !== 'keyOrKeyArray') {
29+
return false;
30+
}
31+
32+
// JWT v5.5.0 already second parameter named `$keyOrKeyArray`, detect by third param (future compatibility)
33+
return isset($params[2]) === false || $params[2]->getName() !== 'allowed_algs';
34+
}
35+
36+
public function decode(string $jwt, $key, string $alg): stdClass
37+
{
38+
return JWT::decode($jwt, new Key($key, $alg));
39+
}
40+
41+
42+
}

src/Plugin/JWT/JWTImpl.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/**
4+
* The MIT License (MIT)
5+
* Copyright (c) 2022 Redbit s.r.o., Jakub Bouček
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Redbitcz\DebugMode\Plugin\JWT;
11+
12+
use stdClass;
13+
14+
interface JWTImpl
15+
{
16+
public static function isAvailable(): bool;
17+
18+
public function decode(string $jwt, $key, string $alg): stdClass;
19+
20+
public function encode(array $payload, $key, string $alg): string;
21+
22+
public function setTimestamp(?int $timestamp): void;
23+
}

src/Plugin/SignedUrl.php

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
<?php
2+
23
/**
34
* The MIT License (MIT)
45
* Copyright (c) 2022 Redbit s.r.o., Jakub Bouček
6+
*
7+
* @noinspection PhpComposerExtensionStubsInspection OpenSSL only optional dependency
8+
* @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection PHP 7 compatibility
59
*/
610

711
declare(strict_types=1);
812

913
namespace Redbitcz\DebugMode\Plugin;
1014

1115
use DateTimeInterface;
12-
use Firebase\JWT\JWT;
1316
use LogicException;
1417
use Nette\Utils\DateTime;
18+
use OpenSSLAsymmetricKey;
19+
use OpenSSLCertificate;
1520
use Redbitcz\DebugMode\Detector;
21+
use Redbitcz\DebugMode\Plugin\JWT\JWTFirebaseV5;
22+
use Redbitcz\DebugMode\Plugin\JWT\JWTFirebaseV6;
23+
use Redbitcz\DebugMode\Plugin\JWT\JWTImpl;
1624
use RuntimeException;
1725

1826
/**
@@ -37,21 +45,32 @@ class SignedUrl implements Plugin
3745
private const URL_QUERY_TOKEN_KEY = '_debug';
3846
private const ISSUER_ID = 'cz.redbit.debug.url';
3947

40-
/** @var resource|string */
48+
/** @var resource|string|OpenSSLAsymmetricKey|OpenSSLCertificate */
4149
private $key;
4250
private string $algorithm;
4351
private ?string $audience;
4452
private ?int $timestamp;
4553

54+
private JWTImpl $jwt;
55+
4656
/**
47-
* @param string|resource $key The key.
57+
* @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The key.
4858
* @param string $algorithm Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
4959
* @param string|null $audience Recipient for which the JWT is intended
60+
* @noinspection PhpRedundantVariableDocTypeInspection
5061
*/
5162
public function __construct($key, string $algorithm = 'HS256', ?string $audience = null)
5263
{
53-
if (class_exists(JWT::class) === false) {
54-
throw new LogicException(__CLASS__ . ' requires JWT library: firebase/php-jwt');
64+
/** @var class-string<JWTImpl> $impl */
65+
foreach ([JWTFirebaseV5::class, JWTFirebaseV6::class] as $impl) {
66+
if ($impl::isAvailable()) {
67+
$this->jwt = new $impl;
68+
break;
69+
}
70+
}
71+
72+
if (isset($this->jwt) === false) {
73+
throw new LogicException(__CLASS__ . ' requires JWT library: firebase/php-jwt version ~5.0 or ~6.0');
5574
}
5675

5776
$this->key = $key;
@@ -115,7 +134,7 @@ public function getToken(
115134
'val' => $value,
116135
];
117136

118-
return JWT::encode($payload, $this->key, $this->algorithm);
137+
return $this->jwt->encode($payload, $this->key, $this->algorithm);
119138
}
120139

121140
public function __invoke(Detector $detector): ?bool
@@ -153,7 +172,7 @@ public function verifyRequest(bool $allowRedirect = false, ?string $url = null,
153172
$url = $url ?? $this->urlFromGlobal();
154173
$method = $method ?? $_SERVER['REQUEST_METHOD'];
155174

156-
[$allowedMethods, $mode, $value, $expires] = $this->verifyUrl($url);
175+
[$allowedMethods, $mode, $value, $expires] = $this->verifyUrl($url, $allowRedirect);
157176

158177
if (in_array(strtolower($method), $allowedMethods, true) === false) {
159178
throw new SignedUrlVerificationException('HTTP method doesn\'t match signed HTTP method');
@@ -227,7 +246,7 @@ public function verifyToken(string $token): array
227246
{
228247
try {
229248
/** @var ClaimsSet $payload */
230-
$payload = JWT::decode($token, $this->key, [$this->algorithm]);
249+
$payload = $this->jwt->decode($token, $this->key, $this->algorithm);
231250
} catch (RuntimeException $e) {
232251
throw new SignedUrlVerificationException('JWT Token invalid', 0, $e);
233252
}
@@ -324,6 +343,11 @@ public function setTimestamp(?int $timestamp): void
324343
$this->timestamp = $timestamp;
325344
}
326345

346+
public function getJwt(): JWTImpl
347+
{
348+
return $this->jwt;
349+
}
350+
327351
protected function sendRedirectResponse(string $canonicalUrl): void
328352
{
329353
header('Cache-Control: s-maxage=0, max-age=0, must-revalidate', true, 302);

0 commit comments

Comments
 (0)