Add test coverage for the SSH key endpoints

This commit is contained in:
DaneEveritt 2022-05-14 18:08:48 -04:00
parent 97280a62a2
commit 6554164252
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
7 changed files with 201 additions and 7 deletions

View file

@ -4,7 +4,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
use Pterodactyl\Transformers\Api\Client\SSHKeyTransformer;
use Pterodactyl\Transformers\Api\Client\UserSSHKeyTransformer;
use Pterodactyl\Http\Requests\Api\Client\Account\StoreSSHKeyRequest;
class SSHKeyController extends ClientApiController
@ -16,7 +16,7 @@ class SSHKeyController extends ClientApiController
public function index(ClientApiRequest $request): array
{
return $this->fractal->collection($request->user()->sshKeys)
->transformWith($this->getTransformer(SSHKeyTransformer::class))
->transformWith($this->getTransformer(UserSSHKeyTransformer::class))
->toArray();
}
@ -32,7 +32,7 @@ class SSHKeyController extends ClientApiController
]);
return $this->fractal->item($model)
->transformWith($this->getTransformer(SSHKeyTransformer::class))
->transformWith($this->getTransformer(UserSSHKeyTransformer::class))
->toArray();
}

View file

@ -43,11 +43,11 @@ class StoreSSHKeyRequest extends ClientApiRequest
}
if ($this->key instanceof DSA) {
$this->validator->errors()->add('public_key', 'DSA public keys are not supported.');
$this->validator->errors()->add('public_key', 'DSA keys are not supported.');
}
if ($this->key instanceof RSA && $this->key->getLength() < 2048) {
$this->validator->errors()->add('public_key', 'RSA keys must be at 2048 bytes.');
$this->validator->errors()->add('public_key', 'RSA keys must be at least 2048 bytes in length.');
}
$fingerprint = $this->key->getFingerprint('sha256');

View file

@ -17,7 +17,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property \Pterodactyl\Models\User $user
*
* @method static \Illuminate\Database\Eloquent\Builder|UserSSHKey newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserSSHKey newQuery()
* @method static \Illuminate\Database\Query\Builder|UserSSHKey onlyTrashed()
@ -33,6 +32,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @method static \Illuminate\Database\Query\Builder|UserSSHKey withTrashed()
* @method static \Illuminate\Database\Query\Builder|UserSSHKey withoutTrashed()
* @mixin \Eloquent
* @method static \Database\Factories\UserSSHKeyFactory factory(...$parameters)
*/
class UserSSHKey extends Model
{

View file

@ -4,7 +4,7 @@ namespace Pterodactyl\Transformers\Api\Client;
use Pterodactyl\Models\UserSSHKey;
class SSHKeyTransformer extends BaseClientTransformer
class UserSSHKeyTransformer extends BaseClientTransformer
{
public function getResourceName(): string
{

View file

@ -0,0 +1,52 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserSSHKeyFactory extends Factory
{
/**
* Returns a fake public key for use on the system.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->name(),
'public_key' => 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOaXIq09NH4a93EVdrvHYiZ67Wj+GBEBQ9ou4W0qSYm2',
'fingerprint' => 'T2b3VnvHWUmfhDOFLqzZg2VfLgqJhWxzrMUy8WZ+V8M',
];
}
/**
* Returns a DSA public key.
*/
public function dsa(): self
{
return $this->state([
'public_key' => 'ssh-dss AAAAB3NzaC1kc3MAAACBAPfiWwEFvBOafdUmHDPjXsUttt+65FHSZSCVVeEFOTaL7Y3d0CJyrtck8KS1vmXHSb8QFBY2B1yVSb/reaQvNreWZN3KDYfLbF57/zimBn+IrHrJR+ZglhOxDRHoGPWK7q9jYIrOLwoOjkNKXxz1eOHKUgufFfSNtIRLycEXczLrAAAAFQC6LnBErezotG52jN4JostfC/TfEwAAAIACuTxRzYFDXHAxFICeqqY9w+y+v2yQfdeQ1BgCq2GMagUYfOdqnjizTO9M614r/nXZK1SV10TqhUcQtkJzDQIUtBqzBF5cIC/1cIFKzXi5rNHs8Y4bz/PBD+EbQJdiy+1so1oi790r710bqnkzTravAOJ5rGyfuQRLt+f+kuS9NAAAAIEA7tjGtJuXGUtPIXfnrMYS1iOWryO4irqnvaWfel002/DaGaNjRghNe/cUBYlAsjPhGJ1F7BQlLAY1koliTY6l0svs7ZPBM5QOumrr8OaNXGGVIq/RkkxuZHmRoUL2qH3DGYaktPUn4vFPliiAmGWOHAEu1K6B4g4vG/SKgMRpIvc=',
'fingerprint' => '9Fb/RODt9N6aldcB+lc6ih0ovr2G/JUjts2Wh21uxYI',
]);
}
/**
* Returns an RSA public key, if "weak" is specified a 1024-bit RSA key is returned
* which should fail validation when being stored.
*/
public function rsa(bool $weak = false): self
{
if (!$weak) {
return $this->state([
'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCo/YLm2SPSlOIG7AagBSmEe5c0b2PLPzUGFp3gARhD6n6ydBS40TlWzeg2qV95lh6fWBd8LsNgPOFmmuKuNZdBjAGeTY4gxKfHY1vK5/zOI4jPPqAMcCMNfd82aM97kx6dO8Hw1R79OyVpOZylpXLHayVPGHUK37Tpih4W7TeVSMrOqQF9F72lzhwgEtkdjm4gLBL6RpdNXrdnjIaNVnuade0Sb3w384vecZPe+S/997WirOMNy2JU4NdMHEnSjd1/i463RpN96AsXFAu1zl9nrXVhA7DVfSHoigXAqbs/xav8PRpLgAKjYpPohxQ9Nu6tP5jRUhfWdYwNFFp/aWloD/0JdP9LqcBBc9sO9TLkz3fBiUf11VM/QT1UhO84G+ahMxVn95jA472VPUe8uKff69lzbvSavEE6qcQX2TzVKOSi1E26Fzc6IZ/tHEuGEbGFxTsiQ1GysVZ0wr1p6ftd1SVqH5F/oaEK7UO8+xn/syEqaPf6A0eJWRNc0+lHA1sIRjmo9MOBvbkKExkx5JLHgGG81DYDFdZUuHY1BgSxJJcmNWV5BKRm350EbgRngoYI5tB3tCiZVW1PI8qyff9mBae11LY5GPlUeDnPrMvSdCKMIWrg7nC8SbndBCO3Fx4z7G2dTQy4ZmY7Ae9jR4pyg7tTOI3qgl8Z462GZi/jzw==',
'fingerprint' => 'vjccQdGfqAvuEK3Bki1Ji6aOo3rIuGU0BGJ0ml4CjLQ',
]);
}
return $this->state([
'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC4VVsHFO5MxvCtAPyoKGANWyuwZ4fvllvFog5RJbfpDpw8etDFVGEXl+uRR8p79g9oV7MscoFo6HiWrJc4/4vlP665msjosILdIcbnuzMhvXnKisaGh9zflkpyR3KhUxoHxqYp2q8XtffjKKAHz1a8o7OUG6fwaKIqu+d0PoICZQ==',
'fingerprint' => 'LQSzAAfAsbKpU94gojxXfjGjYcEv8UZIwyzwhcEr/aw',
]);
}
}

View file

@ -15,6 +15,7 @@ use Pterodactyl\Models\Location;
use Pterodactyl\Models\Schedule;
use Illuminate\Support\Collection;
use Pterodactyl\Models\Allocation;
use Pterodactyl\Models\UserSSHKey;
use Pterodactyl\Models\DatabaseHost;
use Pterodactyl\Tests\Integration\TestResponse;
use Pterodactyl\Tests\Integration\IntegrationTestCase;
@ -77,6 +78,9 @@ abstract class ClientApiIntegrationTestCase extends IntegrationTestCase
case Backup::class:
$link = "/api/client/servers/{$model->server->uuid}/backups/{$model->uuid}";
break;
case UserSSHKey::class:
$link = "/api/client/account/ssh-keys/$model->fingerprint";
break;
default:
throw new InvalidArgumentException(sprintf('Cannot create link for Model of type %s', class_basename($model)));
}

View file

@ -0,0 +1,138 @@
<?php
namespace Pterodactyl\Tests\Integration\Api\Client;
use phpseclib3\Crypt\EC;
use Pterodactyl\Models\User;
use Pterodactyl\Models\UserSSHKey;
class SSHKeyControllerTest extends ClientApiIntegrationTestCase
{
/**
* Test that only the SSH keys for the authenticated user are returned.
*/
public function testSSHKeysAreReturned()
{
$user = User::factory()->create();
$user2 = User::factory()->create();
$key = UserSSHKey::factory()->for($user)->create();
UserSSHKey::factory()->for($user2)->rsa()->create();
$this->actingAs($user);
$response = $this->getJson('/api/client/account/ssh-keys')
->assertOk()
->assertJsonPath('object', 'list')
->assertJsonPath('data.0.object', UserSSHKey::RESOURCE_NAME);
$this->assertJsonTransformedWith($response->json('data.0.attributes'), $key);
}
/**
* Test that a user's SSH key can be deleted, and that passing the fingerprint
* of another user's SSH key won't delete that key.
*/
public function testSSHKeyCanBeDeleted()
{
$user = User::factory()->create();
$user2 = User::factory()->create();
$key = UserSSHKey::factory()->for($user)->create();
$key2 = UserSSHKey::factory()->for($user2)->create();
$this->actingAs($user);
$this->deleteJson($this->link($key))->assertNoContent();
$this->assertSoftDeleted($key);
$this->assertNotSoftDeleted($key2);
$this->deleteJson($this->link($key))->assertNoContent();
$this->deleteJson($this->link($key2))->assertNoContent();
$this->assertNotSoftDeleted($key2);
}
public function testDSAKeyIsRejected()
{
$user = User::factory()->create();
$key = UserSSHKey::factory()->dsa()->make();
$this->actingAs($user)->postJson('/api/client/account/ssh-keys', [
'name' => 'Name',
'public_key' => $key->public_key,
])
->assertUnprocessable()
->assertJsonPath('errors.0.detail', 'DSA keys are not supported.');
$this->assertEquals(0, $user->sshKeys()->count());
}
public function testWeakRSAKeyIsRejected()
{
$user = User::factory()->create();
$key = UserSSHKey::factory()->rsa(true)->make();
$this->actingAs($user)->postJson('/api/client/account/ssh-keys', [
'name' => 'Name',
'public_key' => $key->public_key,
])
->assertUnprocessable()
->assertJsonPath('errors.0.detail', 'RSA keys must be at least 2048 bytes in length.');
$this->assertEquals(0, $user->sshKeys()->count());
}
public function testInvalidOrPrivateKeyIsRejected()
{
$user = User::factory()->create();
$this->actingAs($user)->postJson('/api/client/account/ssh-keys', [
'name' => 'Name',
'public_key' => 'invalid',
])
->assertUnprocessable()
->assertJsonPath('errors.0.detail', 'The public key provided is not valid.');
$this->assertEquals(0, $user->sshKeys()->count());
$key = EC::createKey('Ed25519');
$this->actingAs($user)->postJson('/api/client/account/ssh-keys', [
'name' => 'Name',
'public_key' => $key->toString('PKCS8'),
])
->assertUnprocessable()
->assertJsonPath('errors.0.detail', 'The public key provided is not valid.');
}
public function testPublicKeyCanBeStored()
{
$user = User::factory()->create();
$key = UserSSHKey::factory()->make();
$this->actingAs($user)->postJson('/api/client/account/ssh-keys', [
'name' => 'Name',
'public_key' => $key->public_key,
])
->assertOk()
->assertJsonPath('object', UserSSHKey::RESOURCE_NAME)
->assertJsonPath('attributes.public_key', $key->public_key);
$this->assertCount(1, $user->sshKeys);
$this->assertEquals($key->public_key, $user->sshKeys[0]->public_key);
}
public function testPublicKeyThatAlreadyExistsCannotBeAddedASecondTime()
{
$user = User::factory()->create();
$key = UserSSHKey::factory()->for($user)->create();
$this->actingAs($user)->postJson('/api/client/account/ssh-keys', [
'name' => 'Name',
'public_key' => $key->public_key,
])
->assertUnprocessable()
->assertJsonPath('errors.0.detail', 'The public key provided already exists on your account.');
$this->assertEquals(1, $user->sshKeys()->count());
}
}