Path: blob/1.0-develop/tests/Integration/Api/Client/Server/WebsocketControllerTest.php
7461 views
<?php12namespace Pterodactyl\Tests\Integration\Api\Client\Server;34use Carbon\CarbonImmutable;5use Illuminate\Http\Response;6use Lcobucci\JWT\Configuration;7use Pterodactyl\Models\Permission;8use Lcobucci\JWT\Signer\Hmac\Sha256;9use Lcobucci\JWT\Signer\Key\InMemory;10use Lcobucci\JWT\Validation\Constraint\SignedWith;11use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;1213class WebsocketControllerTest extends ClientApiIntegrationTestCase14{15/**16* Test that a subuser attempting to connect to the websocket receives an error if they17* do not explicitly have the permission.18*/19public function testSubuserWithoutWebsocketPermissionReceivesError()20{21[$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_RESTART]);2223$this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket")24->assertStatus(Response::HTTP_FORBIDDEN)25->assertJsonPath('errors.0.code', 'HttpForbiddenException')26->assertJsonPath('errors.0.detail', 'You do not have permission to connect to this server\'s websocket.');27}2829/**30* Confirm users cannot access the websocket for another user's server.31*/32public function testUserWithoutPermissionForServerReceivesError()33{34[, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]);35[$user] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]);3637$this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket")38->assertStatus(Response::HTTP_NOT_FOUND);39}4041/**42* Test that the expected permissions are returned for the server owner and that the JWT is43* configured correctly.44*/45public function testJwtAndWebsocketUrlAreReturnedForServerOwner()46{47/** @var \Pterodactyl\Models\User $user */48/** @var \Pterodactyl\Models\Server $server */49[$user, $server] = $this->generateTestAccount();5051// Force the node to HTTPS since we want to confirm it gets transformed to wss:// in the URL.52$server->node->scheme = 'https';53$server->node->save();5455$response = $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket");5657$response->assertOk();58$response->assertJsonStructure(['data' => ['token', 'socket']]);5960$connection = $response->json('data.socket');61$this->assertStringStartsWith('wss://', $connection, 'Failed asserting that websocket connection address has expected "wss://" prefix.');62$this->assertStringEndsWith("/api/servers/$server->uuid/ws", $connection, 'Failed asserting that websocket connection address uses expected Wings endpoint.');6364$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey()));65$config->setValidationConstraints(new SignedWith(new Sha256(), $key));66/** @var \Lcobucci\JWT\Token\Plain $token */67$token = $config->parser()->parse($response->json('data.token'));6869$this->assertTrue(70$config->validator()->validate($token, ...$config->validationConstraints()),71'Failed to validate that the JWT data returned was signed using the Node\'s secret key.'72);7374// The way we generate times for the JWT will truncate the microseconds from the75// time, but CarbonImmutable::now() will include them, thus causing test failures.76//77// This little chunk of logic just strips those out by generating a new CarbonImmutable78// instance from the current timestamp, which is how the JWT works. We also need to79// switch to UTC here for consistency.80$expect = CarbonImmutable::createFromTimestamp(CarbonImmutable::now()->getTimestamp())->timezone('UTC');8182// Check that the claims are generated correctly.83$this->assertTrue($token->hasBeenIssuedBy(config('app.url')));84$this->assertTrue($token->isPermittedFor($server->node->getConnectionAddress()));85$this->assertEquals($expect, $token->claims()->get('iat'));86$this->assertEquals($expect->subMinutes(5), $token->claims()->get('nbf'));87$this->assertEquals($expect->addMinutes(10), $token->claims()->get('exp'));88$this->assertSame($user->id, $token->claims()->get('user_id'));89$this->assertSame($server->uuid, $token->claims()->get('server_uuid'));90$this->assertSame(['*'], $token->claims()->get('permissions'));91}9293/**94* Test that the subuser's permissions are passed along correctly in the generated JWT.95*/96public function testJwtIsConfiguredCorrectlyForServerSubuser()97{98$permissions = [Permission::ACTION_WEBSOCKET_CONNECT, Permission::ACTION_CONTROL_CONSOLE];99100/** @var \Pterodactyl\Models\User $user */101/** @var \Pterodactyl\Models\Server $server */102[$user, $server] = $this->generateTestAccount($permissions);103104$response = $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket");105106$response->assertOk();107$response->assertJsonStructure(['data' => ['token', 'socket']]);108109$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey()));110$config->setValidationConstraints(new SignedWith(new Sha256(), $key));111/** @var \Lcobucci\JWT\Token\Plain $token */112$token = $config->parser()->parse($response->json('data.token'));113114$this->assertTrue(115$config->validator()->validate($token, ...$config->validationConstraints()),116'Failed to validate that the JWT data returned was signed using the Node\'s secret key.'117);118119// Check that the claims are generated correctly.120$this->assertSame($permissions, $token->claims()->get('permissions'));121}122}123124125