From 8dbf60de2c0cb891634d1e37bfea4748d3672559 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 20 Jan 2021 21:21:28 -0800 Subject: [PATCH] Update JWT library to support PHP 8; bumps minimum PHP version for Pterodactyl to 7.4 --- app/Services/Nodes/NodeJWTService.php | 14 +- composer.json | 4 +- composer.lock | 144 +++++++++++++----- .../Client/Server/WebsocketControllerTest.php | 38 +++-- 4 files changed, 134 insertions(+), 66 deletions(-) diff --git a/app/Services/Nodes/NodeJWTService.php b/app/Services/Nodes/NodeJWTService.php index b22381fc..2d36904e 100644 --- a/app/Services/Nodes/NodeJWTService.php +++ b/app/Services/Nodes/NodeJWTService.php @@ -3,12 +3,12 @@ namespace Pterodactyl\Services\Nodes; use DateTimeImmutable; -use Lcobucci\JWT\Builder; use Carbon\CarbonImmutable; use Illuminate\Support\Str; -use Lcobucci\JWT\Signer\Key; use Pterodactyl\Models\Node; +use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Hmac\Sha256; +use Lcobucci\JWT\Signer\Key\InMemory; class NodeJWTService { @@ -68,15 +68,15 @@ class NodeJWTService * @param \Pterodactyl\Models\Node $node * @param string|null $identifiedBy * @param string $algo - * @return \Lcobucci\JWT\Token + * @return \Lcobucci\JWT\Token\Plain */ public function handle(Node $node, string $identifiedBy, string $algo = 'md5') { - $signer = new Sha256; - $identifier = hash($algo, $identifiedBy); + $config = Configuration::forSymmetricSigner(new Sha256, InMemory::plainText($node->getDecryptedKey())); - $builder = (new Builder)->issuedBy(config('app.url')) + $builder = $config->builder() + ->issuedBy(config('app.url')) ->permittedFor($node->getConnectionAddress()) ->identifiedBy($identifier) ->withHeader('jti', $identifier) @@ -97,6 +97,6 @@ class NodeJWTService return $builder ->withClaim('unique_id', Str::random(16)) - ->getToken($signer, new Key($node->getDecryptedKey())); + ->getToken($config->signer(), $config->signingKey()); } } diff --git a/composer.json b/composer.json index 75d21cf2..ed463811 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": "^7.3|^8.0", + "php": "^7.4 | ^8.0", "ext-json": "*", "ext-mbstring": "*", "ext-pdo_mysql": "*", @@ -28,7 +28,7 @@ "laravel/helpers": "^1.4", "laravel/tinker": "^2.5", "laravel/ui": "^3.0", - "lcobucci/jwt": "^3.4", + "lcobucci/jwt": "^4.0", "league/flysystem-aws-s3-v3": "^1.0", "league/flysystem-memory": "^1.0", "matriphe/iso-639": "^1.2", diff --git a/composer.lock b/composer.lock index 94be46bc..de51b8e0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f12494defb79b412566ee0f5104e3986", + "content-hash": "47ce788551352ef0f38290c53bce29dd", "packages": [ { "name": "appstract/laravel-blade-directives", @@ -1754,69 +1754,53 @@ "time": "2021-01-06T19:20:22+00:00" }, { - "name": "lcobucci/jwt", - "version": "3.4.2", + "name": "lcobucci/clock", + "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/lcobucci/jwt.git", - "reference": "17cb82dd625ccb17c74bf8f38563d3b260306483" + "url": "https://github.com/lcobucci/clock.git", + "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/17cb82dd625ccb17c74bf8f38563d3b260306483", - "reference": "17cb82dd625ccb17c74bf8f38563d3b260306483", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3", "shasum": "" }, "require": { - "ext-mbstring": "*", - "ext-openssl": "*", - "php": "^5.6 || ^7.0" + "php": "^7.4 || ^8.0" }, "require-dev": { - "mikey179/vfsstream": "~1.5", - "phpmd/phpmd": "~2.2", - "phpunit/php-invoker": "~1.1", - "phpunit/phpunit": "^5.7 || ^7.3", - "squizlabs/php_codesniffer": "~2.3" - }, - "suggest": { - "lcobucci/clock": "*" + "infection/infection": "^0.17", + "lcobucci/coding-standard": "^6.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/php-code-coverage": "9.1.4", + "phpunit/phpunit": "9.3.7" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, "autoload": { "psr-4": { - "Lcobucci\\JWT\\": "src" - }, - "files": [ - "compat/class-aliases.php", - "compat/json-exception-polyfill.php", - "compat/lcobucci-clock-polyfill.php" - ] + "Lcobucci\\Clock\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Luís Otávio Cobucci Oblonczyk", - "email": "lcobucci@gmail.com", - "role": "Developer" + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" } ], - "description": "A simple library to work with JSON Web Token and JSON Web Signature", - "keywords": [ - "JWS", - "jwt" - ], + "description": "Yet another clock abstraction", "support": { - "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/3.4.2" + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/2.0.x" }, "funding": [ { @@ -1828,7 +1812,83 @@ "type": "patreon" } ], - "time": "2020-12-03T13:43:45+00:00" + "time": "2020-08-27T18:56:02+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "6d8665ccd924dc076a9b65d1ea8abe21d68f6958" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/6d8665ccd924dc076a9b65d1ea8abe21d68f6958", + "reference": "6d8665ccd924dc076a9b65d1ea8abe21d68f6958", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-openssl": "*", + "lcobucci/clock": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "infection/infection": "^0.20", + "lcobucci/coding-standard": "^6.0", + "mikey179/vfsstream": "^1.6", + "phpbench/phpbench": "^0.17", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/php-invoker": "^3.1", + "phpunit/phpunit": "^9.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2020-11-25T02:06:12+00:00" }, { "name": "league/commonmark", diff --git a/tests/Integration/Api/Client/Server/WebsocketControllerTest.php b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php index a792c09f..c796196b 100644 --- a/tests/Integration/Api/Client/Server/WebsocketControllerTest.php +++ b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php @@ -3,12 +3,13 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server; use Carbon\Carbon; -use Lcobucci\JWT\Parser; use Carbon\CarbonImmutable; -use Lcobucci\JWT\Signer\Key; use Illuminate\Http\Response; +use Lcobucci\JWT\Configuration; use Pterodactyl\Models\Permission; use Lcobucci\JWT\Signer\Hmac\Sha256; +use Lcobucci\JWT\Signer\Key\InMemory; +use Lcobucci\JWT\Validation\Constraint\SignedWith; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; class WebsocketControllerTest extends ClientApiIntegrationTestCase @@ -52,22 +53,25 @@ class WebsocketControllerTest extends ClientApiIntegrationTestCase $this->assertStringStartsWith('wss://', $connection, 'Failed asserting that websocket connection address has expected "wss://" prefix.'); $this->assertStringEndsWith("/api/servers/{$server->uuid}/ws", $connection, 'Failed asserting that websocket connection address uses expected Wings endpoint.'); - $token = (new Parser)->parse($response->json('data.token')); + $config = Configuration::forSymmetricSigner(new Sha256, $key = InMemory::plainText($server->node->getDecryptedKey())); + $config->setValidationConstraints(new SignedWith(new Sha256, $key)); + /** @var \Lcobucci\JWT\Token\Plain $token */ + $token = $config->parser()->parse($response->json('data.token')); $this->assertTrue( - $token->verify(new Sha256, new Key($server->node->getDecryptedKey())), + $config->validator()->validate($token, ...$config->validationConstraints()), 'Failed to validate that the JWT data returned was signed using the Node\'s secret key.' ); // Check that the claims are generated correctly. - $this->assertSame(config('app.url'), $token->getClaim('iss')); - $this->assertSame($server->node->getConnectionAddress(), $token->getClaim('aud')); - $this->assertSame(CarbonImmutable::now()->getTimestamp(), $token->getClaim('iat')); - $this->assertSame(CarbonImmutable::now()->subMinutes(5)->getTimestamp(), $token->getClaim('nbf')); - $this->assertSame(CarbonImmutable::now()->addMinutes(10)->getTimestamp(), $token->getClaim('exp')); - $this->assertSame($user->id, $token->getClaim('user_id')); - $this->assertSame($server->uuid, $token->getClaim('server_uuid')); - $this->assertSame(['*'], $token->getClaim('permissions')); + $this->assertTrue($token->hasBeenIssuedBy(config('app.url'))); + $this->assertTrue($token->isPermittedFor($server->node->getConnectionAddress())); + $this->assertEquals(CarbonImmutable::now()->toDateTimeImmutable(), $token->claims()->get('iat')); + $this->assertEquals(CarbonImmutable::now()->subMinutes(5)->toDateTimeImmutable(), $token->claims()->get('nbf')); + $this->assertEquals(CarbonImmutable::now()->addMinutes(10)->toDateTimeImmutable(), $token->claims()->get('exp')); + $this->assertSame($user->id, $token->claims()->get('user_id')); + $this->assertSame($server->uuid, $token->claims()->get('server_uuid')); + $this->assertSame(['*'], $token->claims()->get('permissions')); } /** @@ -86,14 +90,18 @@ class WebsocketControllerTest extends ClientApiIntegrationTestCase $response->assertOk(); $response->assertJsonStructure(['data' => ['token', 'socket']]); - $token = (new Parser)->parse($response->json('data.token')); + $config = Configuration::forSymmetricSigner(new Sha256, $key = InMemory::plainText($server->node->getDecryptedKey())); + $config->setValidationConstraints(new SignedWith(new Sha256, $key)); + /** @var \Lcobucci\JWT\Token\Plain $token */ + $token = $config->parser()->parse($response->json('data.token')); $this->assertTrue( - $token->verify(new Sha256, new Key($server->node->getDecryptedKey())), + $config->validator()->validate($token, ...$config->validationConstraints()), 'Failed to validate that the JWT data returned was signed using the Node\'s secret key.' ); + // Check that the claims are generated correctly. - $this->assertSame($permissions, $token->getClaim('permissions')); + $this->assertSame($permissions, $token->claims()->get('permissions')); } }