Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/tests/Integration/Api/Client/Server/WebsocketControllerTest.php
7461 views
1
<?php
2
3
namespace Pterodactyl\Tests\Integration\Api\Client\Server;
4
5
use Carbon\CarbonImmutable;
6
use Illuminate\Http\Response;
7
use Lcobucci\JWT\Configuration;
8
use Pterodactyl\Models\Permission;
9
use Lcobucci\JWT\Signer\Hmac\Sha256;
10
use Lcobucci\JWT\Signer\Key\InMemory;
11
use Lcobucci\JWT\Validation\Constraint\SignedWith;
12
use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;
13
14
class WebsocketControllerTest extends ClientApiIntegrationTestCase
15
{
16
/**
17
* Test that a subuser attempting to connect to the websocket receives an error if they
18
* do not explicitly have the permission.
19
*/
20
public function testSubuserWithoutWebsocketPermissionReceivesError()
21
{
22
[$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_RESTART]);
23
24
$this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket")
25
->assertStatus(Response::HTTP_FORBIDDEN)
26
->assertJsonPath('errors.0.code', 'HttpForbiddenException')
27
->assertJsonPath('errors.0.detail', 'You do not have permission to connect to this server\'s websocket.');
28
}
29
30
/**
31
* Confirm users cannot access the websocket for another user's server.
32
*/
33
public function testUserWithoutPermissionForServerReceivesError()
34
{
35
[, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]);
36
[$user] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]);
37
38
$this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket")
39
->assertStatus(Response::HTTP_NOT_FOUND);
40
}
41
42
/**
43
* Test that the expected permissions are returned for the server owner and that the JWT is
44
* configured correctly.
45
*/
46
public function testJwtAndWebsocketUrlAreReturnedForServerOwner()
47
{
48
/** @var \Pterodactyl\Models\User $user */
49
/** @var \Pterodactyl\Models\Server $server */
50
[$user, $server] = $this->generateTestAccount();
51
52
// Force the node to HTTPS since we want to confirm it gets transformed to wss:// in the URL.
53
$server->node->scheme = 'https';
54
$server->node->save();
55
56
$response = $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket");
57
58
$response->assertOk();
59
$response->assertJsonStructure(['data' => ['token', 'socket']]);
60
61
$connection = $response->json('data.socket');
62
$this->assertStringStartsWith('wss://', $connection, 'Failed asserting that websocket connection address has expected "wss://" prefix.');
63
$this->assertStringEndsWith("/api/servers/$server->uuid/ws", $connection, 'Failed asserting that websocket connection address uses expected Wings endpoint.');
64
65
$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey()));
66
$config->setValidationConstraints(new SignedWith(new Sha256(), $key));
67
/** @var \Lcobucci\JWT\Token\Plain $token */
68
$token = $config->parser()->parse($response->json('data.token'));
69
70
$this->assertTrue(
71
$config->validator()->validate($token, ...$config->validationConstraints()),
72
'Failed to validate that the JWT data returned was signed using the Node\'s secret key.'
73
);
74
75
// The way we generate times for the JWT will truncate the microseconds from the
76
// time, but CarbonImmutable::now() will include them, thus causing test failures.
77
//
78
// This little chunk of logic just strips those out by generating a new CarbonImmutable
79
// instance from the current timestamp, which is how the JWT works. We also need to
80
// switch to UTC here for consistency.
81
$expect = CarbonImmutable::createFromTimestamp(CarbonImmutable::now()->getTimestamp())->timezone('UTC');
82
83
// Check that the claims are generated correctly.
84
$this->assertTrue($token->hasBeenIssuedBy(config('app.url')));
85
$this->assertTrue($token->isPermittedFor($server->node->getConnectionAddress()));
86
$this->assertEquals($expect, $token->claims()->get('iat'));
87
$this->assertEquals($expect->subMinutes(5), $token->claims()->get('nbf'));
88
$this->assertEquals($expect->addMinutes(10), $token->claims()->get('exp'));
89
$this->assertSame($user->id, $token->claims()->get('user_id'));
90
$this->assertSame($server->uuid, $token->claims()->get('server_uuid'));
91
$this->assertSame(['*'], $token->claims()->get('permissions'));
92
}
93
94
/**
95
* Test that the subuser's permissions are passed along correctly in the generated JWT.
96
*/
97
public function testJwtIsConfiguredCorrectlyForServerSubuser()
98
{
99
$permissions = [Permission::ACTION_WEBSOCKET_CONNECT, Permission::ACTION_CONTROL_CONSOLE];
100
101
/** @var \Pterodactyl\Models\User $user */
102
/** @var \Pterodactyl\Models\Server $server */
103
[$user, $server] = $this->generateTestAccount($permissions);
104
105
$response = $this->actingAs($user)->getJson("/api/client/servers/$server->uuid/websocket");
106
107
$response->assertOk();
108
$response->assertJsonStructure(['data' => ['token', 'socket']]);
109
110
$config = Configuration::forSymmetricSigner(new Sha256(), $key = InMemory::plainText($server->node->getDecryptedKey()));
111
$config->setValidationConstraints(new SignedWith(new Sha256(), $key));
112
/** @var \Lcobucci\JWT\Token\Plain $token */
113
$token = $config->parser()->parse($response->json('data.token'));
114
115
$this->assertTrue(
116
$config->validator()->validate($token, ...$config->validationConstraints()),
117
'Failed to validate that the JWT data returned was signed using the Node\'s secret key.'
118
);
119
120
// Check that the claims are generated correctly.
121
$this->assertSame($permissions, $token->claims()->get('permissions'));
122
}
123
}
124
125