Path: blob/1.0-develop/tests/Integration/Api/Remote/SftpAuthenticationControllerTest.php
7461 views
<?php12namespace Pterodactyl\Tests\Integration\Api\Remote;34use phpseclib3\Crypt\EC;5use Pterodactyl\Models\Node;6use Pterodactyl\Models\User;7use Pterodactyl\Models\Server;8use Pterodactyl\Models\Permission;9use Pterodactyl\Models\UserSSHKey;10use Pterodactyl\Tests\Integration\IntegrationTestCase;1112class SftpAuthenticationControllerTest extends IntegrationTestCase13{14protected User $user;1516protected Server $server;1718/**19* Sets up the tests.20*/21public function setUp(): void22{23parent::setUp();2425[$user, $server] = $this->generateTestAccount();2627$user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]);2829$this->user = $user;30$this->server = $server;3132$this->setAuthorization();33}3435/**36* Test that a public key is validated correctly.37*/38public function testPublicKeyIsValidatedCorrectly()39{40$key = UserSSHKey::factory()->for($this->user)->create();4142$this->postJson('/api/remote/sftp/auth', [])43->assertUnprocessable()44->assertJsonPath('errors.0.meta.source_field', 'username')45->assertJsonPath('errors.0.meta.rule', 'required')46->assertJsonPath('errors.1.meta.source_field', 'password')47->assertJsonPath('errors.1.meta.rule', 'required');4849$data = [50'type' => 'public_key',51'username' => $this->getUsername(),52'password' => $key->public_key,53];5455$this->postJson('/api/remote/sftp/auth', $data)56->assertOk()57->assertJsonPath('server', $this->server->uuid)58->assertJsonPath('permissions', ['*']);5960$key->delete();61$this->postJson('/api/remote/sftp/auth', $data)->assertForbidden();62$this->postJson('/api/remote/sftp/auth', array_merge($data, ['type' => null]))->assertForbidden();63}6465/**66* Test that an account password is validated correctly.67*/68public function testPasswordIsValidatedCorrectly()69{70$this->postJson('/api/remote/sftp/auth', [71'username' => $this->getUsername(),72'password' => '',73])74->assertUnprocessable()75->assertJsonPath('errors.0.meta.source_field', 'password')76->assertJsonPath('errors.0.meta.rule', 'required');7778$this->postJson('/api/remote/sftp/auth', [79'username' => $this->getUsername(),80'password' => 'wrong password',81])82->assertForbidden();8384$this->user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]);8586$this->postJson('/api/remote/sftp/auth', [87'username' => $this->getUsername(),88'password' => 'foobar',89])90->assertOk();91}9293/**94* Test that providing an invalid key and/or invalid username triggers the throttle on95* the endpoint.96*/97#[\PHPUnit\Framework\Attributes\DataProvider('authorizationTypeDataProvider')]98public function testUserIsThrottledIfInvalidCredentialsAreProvided()99{100for ($i = 0; $i <= 10; ++$i) {101$this->postJson('/api/remote/sftp/auth', [102'type' => 'public_key',103'username' => $i % 2 === 0 ? $this->user->username : $this->getUsername(),104'password' => 'invalid key',105])106->assertStatus($i === 10 ? 429 : 403);107}108}109110/**111* Test that the user is not throttled so long as a valid public key is provided, even112* if it doesn't actually exist in the database for the user.113*/114public function testUserIsNotThrottledIfNoPublicKeyMatches()115{116for ($i = 0; $i <= 10; ++$i) {117$this->postJson('/api/remote/sftp/auth', [118'type' => 'public_key',119'username' => $this->getUsername(),120'password' => EC::createKey('Ed25519')->getPublicKey()->toString('OpenSSH'),121])122->assertForbidden();123}124}125126/**127* Test that a request is rejected if the credentials are valid but the username indicates128* a server on a different node.129*/130#[\PHPUnit\Framework\Attributes\DataProvider('authorizationTypeDataProvider')]131public function testRequestIsRejectedIfServerBelongsToDifferentNode(string $type)132{133$node2 = $this->createServerModel()->node;134135$this->setAuthorization($node2);136137$password = $type === 'public_key'138? UserSSHKey::factory()->for($this->user)->create()->public_key139: 'foobar';140141$this->postJson('/api/remote/sftp/auth', [142'type' => 'public_key',143'username' => $this->getUsername(),144'password' => $password,145])146->assertForbidden();147}148149public function testRequestIsDeniedIfUserLacksSftpPermission()150{151[$user, $server] = $this->generateTestAccount([Permission::ACTION_FILE_READ]);152153$user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]);154155$this->setAuthorization($server->node);156157$this->postJson('/api/remote/sftp/auth', [158'username' => $user->username . '.' . $server->uuidShort,159'password' => 'foobar',160])161->assertForbidden()162->assertJsonPath('errors.0.detail', 'You do not have permission to access SFTP for this server.');163}164165#[\PHPUnit\Framework\Attributes\DataProvider('serverStateDataProvider')]166public function testInvalidServerStateReturnsConflictError(string $status)167{168$this->server->update(['status' => $status]);169170$this->postJson('/api/remote/sftp/auth', ['username' => $this->getUsername(), 'password' => 'foobar'])171->assertStatus(409);172}173174/**175* Test that permissions are returned for the user account correctly.176*/177public function testUserPermissionsAreReturnedCorrectly()178{179[$user, $server] = $this->generateTestAccount([Permission::ACTION_FILE_READ, Permission::ACTION_FILE_SFTP]);180181$user->update(['password' => password_hash('foobar', PASSWORD_DEFAULT)]);182183$this->setAuthorization($server->node);184185$data = [186'username' => $user->username . '.' . $server->uuidShort,187'password' => 'foobar',188];189190$this->postJson('/api/remote/sftp/auth', $data)191->assertOk()192->assertJsonPath('permissions', [Permission::ACTION_FILE_READ, Permission::ACTION_FILE_SFTP]);193194$user->update(['root_admin' => true]);195196$this->postJson('/api/remote/sftp/auth', $data)197->assertOk()198->assertJsonPath('permissions.0', '*');199200$this->setAuthorization();201$data['username'] = $user->username . '.' . $this->server->uuidShort;202203$this->post('/api/remote/sftp/auth', $data)204->assertOk()205->assertJsonPath('permissions.0', '*');206207$user->update(['root_admin' => false]);208$this->post('/api/remote/sftp/auth', $data)->assertForbidden();209}210211public static function authorizationTypeDataProvider(): array212{213return [214'password auth' => ['password'],215'public key auth' => ['public_key'],216];217}218219public static function serverStateDataProvider(): array220{221return [222'installing' => [Server::STATUS_INSTALLING],223'suspended' => [Server::STATUS_SUSPENDED],224'restoring a backup' => [Server::STATUS_RESTORING_BACKUP],225];226}227228/**229* Returns the username for connecting to SFTP.230*/231protected function getUsername(bool $long = false): string232{233return $this->user->username . '.' . ($long ? $this->server->uuid : $this->server->uuidShort);234}235236/**237* Sets the authorization header for the rest of the test.238*/239protected function setAuthorization(?Node $node = null): void240{241$node = $node ?? $this->server->node;242243$this->withHeader('Authorization', 'Bearer ' . $node->daemon_token_id . '.' . decrypt($node->daemon_token));244}245}246247248