From fc261fe20ccd8859c26839f07ba1b900c5bdc81e Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 27 Jun 2020 10:35:02 -0700 Subject: [PATCH] Add test cases for client servers endpoint --- app/Models/Location.php | 10 ++ database/factories/ModelFactory.php | 38 +--- .../Api/Client/ApiKeyControllerTest.php | 2 +- .../Api/Client/ClientControllerTest.php | 162 ++++++++++++++++++ tests/Integration/IntegrationTestCase.php | 3 + 5 files changed, 178 insertions(+), 37 deletions(-) create mode 100644 tests/Integration/Api/Client/ClientControllerTest.php diff --git a/app/Models/Location.php b/app/Models/Location.php index 71d3b4117..17ba7e24a 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -2,6 +2,16 @@ namespace Pterodactyl\Models; +/** + * @property int $id + * @property string $short + * @property string $long + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\Node[] $nodes + * @property \Pterodactyl\Models\Server[] $servers + */ class Location extends Model { /** diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index db12c08d9..553f8a032 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -20,8 +20,6 @@ use Pterodactyl\Models\ApiKey; $factory->define(Pterodactyl\Models\Server::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'node_id' => $faker->randomNumber(), 'uuid' => $faker->unique()->uuid, 'uuidShort' => str_random(8), 'name' => $faker->firstName, @@ -34,9 +32,6 @@ $factory->define(Pterodactyl\Models\Server::class, function (Faker $faker) { 'io' => 500, 'cpu' => 0, 'oom_disabled' => 0, - 'allocation_id' => $faker->randomNumber(), - 'nest_id' => $faker->randomNumber(), - 'egg_id' => $faker->randomNumber(), 'pack_id' => null, 'installed' => 1, 'database_limit' => null, @@ -50,7 +45,6 @@ $factory->define(Pterodactyl\Models\User::class, function (Faker $faker) { static $password; return [ - 'id' => $faker->unique()->randomNumber(), 'external_id' => $faker->unique()->isbn10, 'uuid' => $faker->uuid, 'username' => $faker->userName, @@ -74,15 +68,13 @@ $factory->state(Pterodactyl\Models\User::class, 'admin', function () { $factory->define(Pterodactyl\Models\Location::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'short' => $faker->unique()->domainWord, + 'short' => Str::random(8), 'long' => $faker->catchPhrase, ]; }); $factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'uuid' => Uuid::uuid4()->toString(), 'public' => true, 'name' => $faker->firstName, @@ -104,7 +96,6 @@ $factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\Nest::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'uuid' => $faker->unique()->uuid, 'author' => 'testauthor@example.com', 'name' => $faker->word, @@ -114,9 +105,7 @@ $factory->define(Pterodactyl\Models\Nest::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\Egg::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'uuid' => $faker->unique()->uuid, - 'nest_id' => $faker->unique()->randomNumber(), 'name' => $faker->name, 'description' => implode(' ', $faker->sentences(3)), 'startup' => 'java -jar test.jar', @@ -125,7 +114,6 @@ $factory->define(Pterodactyl\Models\Egg::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\EggVariable::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'name' => $faker->firstName, 'description' => $faker->sentence(), 'env_variable' => strtoupper(str_replace(' ', '_', $faker->words(2, true))), @@ -146,8 +134,6 @@ $factory->state(Pterodactyl\Models\EggVariable::class, 'editable', function () { $factory->define(Pterodactyl\Models\Pack::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'egg_id' => $faker->randomNumber(), 'uuid' => $faker->uuid, 'name' => $faker->word, 'description' => null, @@ -159,17 +145,11 @@ $factory->define(Pterodactyl\Models\Pack::class, function (Faker $faker) { }); $factory->define(Pterodactyl\Models\Subuser::class, function (Faker $faker) { - return [ - 'id' => $faker->unique()->randomNumber(), - 'user_id' => $faker->randomNumber(), - 'server_id' => $faker->randomNumber(), - ]; + return []; }); $factory->define(Pterodactyl\Models\Allocation::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'node_id' => $faker->randomNumber(), 'ip' => $faker->ipv4, 'port' => $faker->randomNumber(5), ]; @@ -177,13 +157,11 @@ $factory->define(Pterodactyl\Models\Allocation::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\DatabaseHost::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'name' => $faker->colorName, 'host' => $faker->unique()->ipv4, 'port' => 3306, 'username' => $faker->colorName, 'password' => Crypt::encrypt($faker->word), - 'node_id' => $faker->randomNumber(), ]; }); @@ -191,9 +169,6 @@ $factory->define(Pterodactyl\Models\Database::class, function (Faker $faker) { static $password; return [ - 'id' => $faker->unique()->randomNumber(), - 'server_id' => $faker->randomNumber(), - 'database_host_id' => $faker->randomNumber(), 'database' => str_random(10), 'username' => str_random(10), 'remote' => '%', @@ -205,16 +180,12 @@ $factory->define(Pterodactyl\Models\Database::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\Schedule::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'server_id' => $faker->randomNumber(), 'name' => $faker->firstName(), ]; }); $factory->define(Pterodactyl\Models\Task::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'schedule_id' => $faker->randomNumber(), 'sequence_id' => $faker->randomNumber(1), 'action' => 'command', 'payload' => 'test command', @@ -225,9 +196,6 @@ $factory->define(Pterodactyl\Models\Task::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\DaemonKey::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'server_id' => $faker->randomNumber(), - 'user_id' => $faker->randomNumber(), 'secret' => 'i_' . str_random(40), 'expires_at' => \Carbon\Carbon::now()->addMinutes(10)->toDateTimeString(), ]; @@ -237,8 +205,6 @@ $factory->define(Pterodactyl\Models\ApiKey::class, function (Faker $faker) { static $token; return [ - 'id' => $faker->unique()->randomNumber(), - 'user_id' => $faker->randomNumber(), 'key_type' => ApiKey::TYPE_APPLICATION, 'identifier' => str_random(Pterodactyl\Models\ApiKey::IDENTIFIER_LENGTH), 'token' => $token ?: $token = encrypt(str_random(Pterodactyl\Models\ApiKey::KEY_LENGTH)), diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php index 8189342b9..f0b35a519 100644 --- a/tests/Integration/Api/Client/ApiKeyControllerTest.php +++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php @@ -63,7 +63,7 @@ class ApiKeyControllerTest extends IntegrationTestCase /** @var \Pterodactyl\Models\User $user */ $user = factory(User::class)->create(); - // Small sub-test to ensure we're always comparing the number of keys to the + // Small sub-test to ensure we're always comparing the number of keys to the // specific logged in account, and not just the total number of keys stored in // the database. factory(ApiKey::class)->times(10)->create([ diff --git a/tests/Integration/Api/Client/ClientControllerTest.php b/tests/Integration/Api/Client/ClientControllerTest.php new file mode 100644 index 000000000..eb88d122c --- /dev/null +++ b/tests/Integration/Api/Client/ClientControllerTest.php @@ -0,0 +1,162 @@ +forceDelete(); + User::query()->forceDelete(); + Node::query()->forceDelete(); + Location::query()->forceDelete(); + + parent::tearDown(); + } + + /** + * Test that only the servers a logged in user is assigned to are returned by the + * API endpoint. Obviously there are cases such as being an administrator or being + * a subuser, but for this test we just want to test a basic scenario and pretend + * subusers do not exist at all. + */ + public function testOnlyLoggedInUsersServersAreReturned() + { + /** @var \Pterodactyl\Models\User[] $users */ + $users = factory(User::class)->times(3)->create(); + + /** @var \Pterodactyl\Models\Server[] $servers */ + $servers = [ + $this->createServerModel(['user_id' => $users[0]->id]), + $this->createServerModel(['user_id' => $users[1]->id]), + $this->createServerModel(['user_id' => $users[2]->id]), + ]; + + $response = $this->actingAs($users[0])->getJson('/api/client'); + + $response->assertOk(); + $response->assertJsonPath('object', 'list'); + $response->assertJsonPath('data.0.object', Server::RESOURCE_NAME); + $response->assertJsonPath('data.0.attributes.identifier', $servers[0]->uuidShort); + $response->assertJsonPath('data.0.attributes.server_owner', true); + $response->assertJsonPath('meta.pagination.total', 1); + $response->assertJsonPath('meta.pagination.per_page', config('pterodactyl.paginate.frontend.servers')); + } + + /** + * Tests that all of the servers on the system are returned when making the request as an + * administrator and including the ?filter=all parameter in the URL. + */ + public function testFilterIncludeAllServersWhenAdministrator() + { + /** @var \Pterodactyl\Models\User[] $users */ + $users = factory(User::class)->times(3)->create(); + $users[0]->root_admin = true; + + $servers = [ + $this->createServerModel(['user_id' => $users[0]->id]), + $this->createServerModel(['user_id' => $users[1]->id]), + $this->createServerModel(['user_id' => $users[2]->id]), + ]; + + $response = $this->actingAs($users[0])->getJson('/api/client?filter=all'); + + $response->assertOk(); + $response->assertJsonCount(3, 'data'); + + for ($i = 0; $i < 3; $i++) { + $response->assertJsonPath("data.{$i}.attributes.server_owner", $i === 0); + $response->assertJsonPath("data.{$i}.attributes.identifier", $servers[$i]->uuidShort); + } + } + + /** + * Test that servers where the user is a subuser are returned by default in the API call. + */ + public function testServersUserIsASubuserOfAreReturned() + { + /** @var \Pterodactyl\Models\User[] $users */ + $users = factory(User::class)->times(3)->create(); + $servers = [ + $this->createServerModel(['user_id' => $users[0]->id]), + $this->createServerModel(['user_id' => $users[1]->id]), + $this->createServerModel(['user_id' => $users[2]->id]), + ]; + + // Set user 0 as a subuser of server 1. Thus, we should get two servers + // back in the response when making the API call as user 0. + Subuser::query()->create([ + 'user_id' => $users[0]->id, + 'server_id' => $servers[1]->id, + 'permissions' => [Permission::ACTION_WEBSOCKET_CONNECT], + ]); + + $response = $this->actingAs($users[0])->getJson('/api/client'); + + $response->assertOk(); + $response->assertJsonCount(2, 'data'); + $response->assertJsonPath('data.0.attributes.server_owner', true); + $response->assertJsonPath('data.0.attributes.identifier', $servers[0]->uuidShort); + $response->assertJsonPath('data.1.attributes.server_owner', false); + $response->assertJsonPath('data.1.attributes.identifier', $servers[1]->uuidShort); + } + + /** + * Returns only servers that the user owns, not servers they are a subuser of. + */ + public function testFilterOnlyOwnerServers() + { + /** @var \Pterodactyl\Models\User[] $users */ + $users = factory(User::class)->times(3)->create(); + $servers = [ + $this->createServerModel(['user_id' => $users[0]->id]), + $this->createServerModel(['user_id' => $users[1]->id]), + $this->createServerModel(['user_id' => $users[2]->id]), + ]; + + // Set user 0 as a subuser of server 1. Thus, we should get two servers + // back in the response when making the API call as user 0. + Subuser::query()->create([ + 'user_id' => $users[0]->id, + 'server_id' => $servers[1]->id, + 'permissions' => [Permission::ACTION_WEBSOCKET_CONNECT], + ]); + + $response = $this->actingAs($users[0])->getJson('/api/client?filter=owner'); + + $response->assertOk(); + $response->assertJsonCount(1, 'data'); + $response->assertJsonPath('data.0.attributes.server_owner', true); + $response->assertJsonPath('data.0.attributes.identifier', $servers[0]->uuidShort); + } + + /** + * Tests that the permissions from the Panel are returned correctly. + */ + public function testPermissionsAreReturned() + { + /** @var \Pterodactyl\Models\User $user */ + $user = factory(User::class)->create(); + + $this->actingAs($user) + ->getJson('/api/client/permissions') + ->assertOk() + ->assertJson([ + 'object' => 'system_permissions', + 'attributes' => [ + 'permissions' => Permission::permissions()->toArray(), + ], + ]); + } +} diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index cb81ef6c2..ee7229660 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -5,10 +5,13 @@ namespace Pterodactyl\Tests\Integration; use Tests\TestCase; use Cake\Chronos\Chronos; use Illuminate\Database\Eloquent\Model; +use Tests\Traits\Integration\CreatesTestModels; use Pterodactyl\Transformers\Api\Application\BaseTransformer; abstract class IntegrationTestCase extends TestCase { + use CreatesTestModels; + /** * Setup base integration test cases. */