diff --git a/app/Http/Controllers/API/Remote/SftpController.php b/app/Http/Controllers/API/Remote/SftpController.php
new file mode 100644
index 000000000..07582153d
--- /dev/null
+++ b/app/Http/Controllers/API/Remote/SftpController.php
@@ -0,0 +1,85 @@
+authenticationService = $authenticationService;
+ }
+
+ /**
+ * Authenticate a set of credentials and return the associated server details
+ * for a SFTP connection on the daemon.
+ *
+ * @param \Pterodactyl\Http\Requests\API\Remote\SftpAuthenticationFormRequest $request
+ * @return \Illuminate\Http\JsonResponse
+ *
+ * @throws \Pterodactyl\Exceptions\Model\DataValidationException
+ */
+ public function index(SftpAuthenticationFormRequest $request): JsonResponse
+ {
+ $connection = explode('.', $request->input('username'));
+ $this->incrementLoginAttempts($request);
+
+ if ($this->hasTooManyLoginAttempts($request)) {
+ return response()->json([
+ 'error' => 'Logins throttled.',
+ ], 429);
+ }
+
+ try {
+ $data = $this->authenticationService->handle(
+ array_get($connection, 0),
+ $request->input('password'),
+ object_get($request->attributes->get('node'), 'id', 0),
+ array_get($connection, 1)
+ );
+
+ $this->clearLoginAttempts($request);
+ } catch (AuthenticationException $exception) {
+ return response()->json([
+ 'error' => 'Invalid credentials.',
+ ], 403);
+ } catch (RecordNotFoundException $exception) {
+ return response()->json([
+ 'error' => 'Invalid server.',
+ ], 404);
+ }
+
+ return response()->json($data);
+ }
+
+ /**
+ * Get the throttle key for the given request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return string
+ */
+ protected function throttleKey(Request $request)
+ {
+ return strtolower(array_get(explode('.', $request->input('username')), 0) . '|' . $request->ip());
+ }
+}
diff --git a/app/Http/Controllers/Server/Settings/SftpController.php b/app/Http/Controllers/Server/Settings/SftpController.php
new file mode 100644
index 000000000..b128ba5c9
--- /dev/null
+++ b/app/Http/Controllers/Server/Settings/SftpController.php
@@ -0,0 +1,26 @@
+setRequest($request)->injectJavascript();
+
+ return view('server.settings.sftp');
+ }
+}
diff --git a/app/Http/Middleware/Daemon/DaemonAuthenticate.php b/app/Http/Middleware/Daemon/DaemonAuthenticate.php
index 2804fa923..2572ba854 100644
--- a/app/Http/Middleware/Daemon/DaemonAuthenticate.php
+++ b/app/Http/Middleware/Daemon/DaemonAuthenticate.php
@@ -75,7 +75,7 @@ class DaemonAuthenticate
throw new HttpException(403);
}
- $request->attributes->set('node.model', $node);
+ $request->attributes->set('node', $node);
return $next($request);
}
diff --git a/app/Http/Requests/API/Remote/SftpAuthenticationFormRequest.php b/app/Http/Requests/API/Remote/SftpAuthenticationFormRequest.php
new file mode 100644
index 000000000..5d82f55c7
--- /dev/null
+++ b/app/Http/Requests/API/Remote/SftpAuthenticationFormRequest.php
@@ -0,0 +1,44 @@
+ 'required|string',
+ 'password' => 'required|string',
+ ];
+ }
+
+ /**
+ * Return only the fields that we are interested in from the request.
+ * This will include empty fields as a null value.
+ *
+ * @return array
+ */
+ public function normalize()
+ {
+ return $this->only(
+ array_keys($this->rules())
+ );
+ }
+}
diff --git a/app/Models/Node.php b/app/Models/Node.php
index 368c6f3d8..71f5614b5 100644
--- a/app/Models/Node.php
+++ b/app/Models/Node.php
@@ -144,13 +144,23 @@ class Node extends Model implements CleansAttributes, ValidableContract
],
],
'docker' => [
+ 'container' => [
+ 'user' => null,
+ ],
+ 'network' => [
+ 'name' => 'pterodactyl_nw',
+ ],
'socket' => '/var/run/docker.sock',
'autoupdate_images' => true,
],
'sftp' => [
'path' => $this->daemonBase,
+ 'ip' => '0.0.0.0',
'port' => $this->daemonSFTP,
- 'container' => 'ptdl-sftp',
+ 'keypair' => [
+ 'bits' => 2048,
+ 'e' => 65537,
+ ],
],
'logger' => [
'path' => 'logs/',
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 09563baf1..04ac19e43 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -29,19 +29,12 @@ class Server extends Model implements CleansAttributes, ValidableContract
*/
protected $table = 'servers';
- /**
- * The attributes excluded from the model's JSON form.
- *
- * @var array
- */
- protected $hidden = ['sftp_password'];
-
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
- protected $dates = ['deleted_at'];
+ protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'deleted_at'];
/**
* Always eager load these relationships on the model.
@@ -55,7 +48,7 @@ class Server extends Model implements CleansAttributes, ValidableContract
*
* @var array
*/
- protected $guarded = ['id', 'installed', 'created_at', 'updated_at', 'deleted_at'];
+ protected $guarded = ['id', 'installed', self::CREATED_AT, self::UPDATED_AT, 'deleted_at'];
/**
* @var array
@@ -73,8 +66,6 @@ class Server extends Model implements CleansAttributes, ValidableContract
'node_id' => 'required',
'allocation_id' => 'required',
'pack_id' => 'sometimes',
- 'auto_deploy' => 'sometimes',
- 'custom_id' => 'sometimes',
'skip_scripts' => 'sometimes',
];
@@ -95,10 +86,7 @@ class Server extends Model implements CleansAttributes, ValidableContract
'nest_id' => 'exists:nests,id',
'egg_id' => 'exists:eggs,id',
'pack_id' => 'nullable|numeric|min:0',
- 'custom_container' => 'nullable|string',
'startup' => 'nullable|string',
- 'auto_deploy' => 'accepted',
- 'custom_id' => 'numeric|unique:servers,id',
'skip_scripts' => 'boolean',
];
@@ -132,7 +120,6 @@ class Server extends Model implements CleansAttributes, ValidableContract
*/
protected $searchableColumns = [
'name' => 10,
- 'username' => 10,
'uuidShort' => 9,
'uuid' => 8,
'pack.name' => 7,
diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php
index 0994abe55..33d23b000 100644
--- a/app/Services/Servers/ServerCreationService.php
+++ b/app/Services/Servers/ServerCreationService.php
@@ -63,11 +63,6 @@ class ServerCreationService
*/
protected $userRepository;
- /**
- * @var \Pterodactyl\Services\Servers\UsernameGenerationService
- */
- protected $usernameService;
-
/**
* @var \Pterodactyl\Services\Servers\VariableValidatorService
*/
@@ -84,7 +79,6 @@ class ServerCreationService
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository
- * @param \Pterodactyl\Services\Servers\UsernameGenerationService $usernameService
* @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService
*/
public function __construct(
@@ -96,7 +90,6 @@ class ServerCreationService
ServerRepositoryInterface $repository,
ServerVariableRepositoryInterface $serverVariableRepository,
UserRepositoryInterface $userRepository,
- UsernameGenerationService $usernameService,
VariableValidatorService $validatorService
) {
$this->allocationRepository = $allocationRepository;
@@ -107,7 +100,6 @@ class ServerCreationService
$this->repository = $repository;
$this->serverVariableRepository = $serverVariableRepository;
$this->userRepository = $userRepository;
- $this->usernameService = $usernameService;
$this->validatorService = $validatorService;
}
@@ -151,8 +143,6 @@ class ServerCreationService
'startup' => $data['startup'],
'daemonSecret' => str_random(NodeCreationService::DAEMON_SECRET_LENGTH),
'image' => $data['docker_image'],
- 'username' => $this->usernameService->generate($data['name'], $uniqueShort),
- 'sftp_password' => null,
]);
// Process allocations and assign them to the server in the database.
diff --git a/app/Services/Servers/UsernameGenerationService.php b/app/Services/Servers/UsernameGenerationService.php
deleted file mode 100644
index 3fb2c6d3b..000000000
--- a/app/Services/Servers/UsernameGenerationService.php
+++ /dev/null
@@ -1,40 +0,0 @@
-.
- *
- * This software is licensed under the terms of the MIT license.
- * https://opensource.org/licenses/MIT
- */
-
-namespace Pterodactyl\Services\Servers;
-
-class UsernameGenerationService
-{
- /**
- * Generate a unique username to be used for SFTP connections and identification
- * of the server docker container on the host system.
- *
- * @param string $name
- * @param null $identifier
- * @return string
- */
- public function generate($name, $identifier = null)
- {
- if (is_null($identifier) || ! ctype_alnum($identifier)) {
- $unique = str_random(8);
- } else {
- if (strlen($identifier) < 8) {
- $unique = $identifier . str_random((8 - strlen($identifier)));
- } else {
- $unique = substr($identifier, 0, 8);
- }
- }
-
- // Filter the Server Name
- $name = trim(preg_replace('/[^A-Za-z0-9]+/', '', $name), '_');
- $name = (strlen($name) < 1) ? str_random(6) : $name;
-
- return strtolower(substr($name, 0, 6) . '_' . $unique);
- }
-}
diff --git a/app/Services/Sftp/AuthenticateUsingPasswordService.php b/app/Services/Sftp/AuthenticateUsingPasswordService.php
new file mode 100644
index 000000000..487d251d4
--- /dev/null
+++ b/app/Services/Sftp/AuthenticateUsingPasswordService.php
@@ -0,0 +1,90 @@
+keyProviderService = $keyProviderService;
+ $this->repository = $repository;
+ $this->userRepository = $userRepository;
+ }
+
+ /**
+ * Attempt to authenticate a provded username and password and determine if they
+ * have permission to access a given server. This function does not account for
+ * subusers currently. Only administrators and server owners can login to access
+ * their files at this time.
+ *
+ * Server must exist on the node that the API call is being made from in order for a
+ * valid response to be provided.
+ *
+ * @param string $username
+ * @param string $password
+ * @param string|null $server
+ * @param int $node
+ * @return array
+ *
+ * @throws \Illuminate\Auth\AuthenticationException
+ * @throws \Pterodactyl\Exceptions\Model\DataValidationException
+ * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
+ */
+ public function handle(string $username, string $password, int $node, string $server = null): array
+ {
+ if (is_null($server)) {
+ throw new RecordNotFoundException;
+ }
+
+ try {
+ $user = $this->userRepository->withColumns(['id', 'root_admin', 'password'])->findFirstWhere([['username', '=', $username]]);
+
+ if (! password_verify($password, $user->password)) {
+ throw new AuthenticationException;
+ }
+ } catch (RecordNotFoundException $exception) {
+ throw new AuthenticationException;
+ }
+
+ $server = $this->repository->withColumns(['id', 'node_id', 'owner_id', 'uuid'])->getByUuid($server);
+ if ($server->node_id !== $node || (! $user->root_admin && $server->owner_id !== $user->id)) {
+ throw new RecordNotFoundException;
+ }
+
+ return [
+ 'server' => $server->uuid,
+ 'token' => $this->keyProviderService->handle($server->id, $user->id),
+ ];
+ }
+}
diff --git a/app/Traits/Controllers/JavascriptInjection.php b/app/Traits/Controllers/JavascriptInjection.php
index 7c7ee3c16..c6efc86ac 100644
--- a/app/Traits/Controllers/JavascriptInjection.php
+++ b/app/Traits/Controllers/JavascriptInjection.php
@@ -50,7 +50,6 @@ trait JavascriptInjection
'uuid' => $server->uuid,
'uuidShort' => $server->uuidShort,
'daemonSecret' => $token,
- 'username' => $server->username,
],
'node' => [
'fqdn' => $server->node->fqdn,
diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php
index 82d7ad8b0..233aeee01 100644
--- a/database/factories/ModelFactory.php
+++ b/database/factories/ModelFactory.php
@@ -30,8 +30,6 @@ $factory->define(Pterodactyl\Models\Server::class, function (Faker\Generator $fa
'cpu' => 0,
'oom_disabled' => 0,
'pack_id' => null,
- 'username' => $faker->userName,
- 'sftp_password' => null,
'installed' => 1,
'created_at' => \Carbon\Carbon::now(),
'updated_at' => \Carbon\Carbon::now(),
diff --git a/database/migrations/2017_10_24_222238_RemoveLegacySFTPInformation.php b/database/migrations/2017_10_24_222238_RemoveLegacySFTPInformation.php
new file mode 100644
index 000000000..e41acd275
--- /dev/null
+++ b/database/migrations/2017_10_24_222238_RemoveLegacySFTPInformation.php
@@ -0,0 +1,32 @@
+dropUnique(['username']);
+
+ $table->dropColumn('username');
+ $table->dropColumn('sftp_password');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down()
+ {
+ Schema::table('servers', function (Blueprint $table) {
+ $table->string('username')->nullable()->after('image')->unique();
+ $table->text('sftp_password')->after('image');
+ });
+ }
+}
diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php
index 489803b87..3ed89c511 100644
--- a/resources/lang/en/server.php
+++ b/resources/lang/en/server.php
@@ -301,7 +301,7 @@ return [
'change_pass' => 'Change SFTP Password',
'details' => 'SFTP Details',
'conn_addr' => 'Connection Address',
- 'warning' => 'Ensure that your client is set to use SFTP and not FTP or FTPS for connections, there is a difference between the protocols.',
+ 'warning' => 'The SFTP password is your account password. Ensure that your client is set to use SFTP and not FTP or FTPS for connections, there is a difference between the protocols.',
],
'database' => [
'header' => 'Databases',
diff --git a/resources/themes/pterodactyl/admin/servers/index.blade.php b/resources/themes/pterodactyl/admin/servers/index.blade.php
index 1c066a4a0..5619ea2f8 100644
--- a/resources/themes/pterodactyl/admin/servers/index.blade.php
+++ b/resources/themes/pterodactyl/admin/servers/index.blade.php
@@ -42,7 +42,6 @@
ID |
Server Name |
Owner |
- Username |
Node |
Connection |
|
@@ -52,7 +51,6 @@
{{ $server->uuidShort }} |
{{ $server->name }} |
{{ $server->user->username }} |
- {{ $server->username }} |
{{ $server->node->name }} |
{{ $server->allocation->alias }}:{{ $server->allocation->port }}
diff --git a/resources/themes/pterodactyl/admin/servers/view/index.blade.php b/resources/themes/pterodactyl/admin/servers/view/index.blade.php
index 81d73d06e..d5020bcdf 100644
--- a/resources/themes/pterodactyl/admin/servers/view/index.blade.php
+++ b/resources/themes/pterodactyl/admin/servers/view/index.blade.php
@@ -55,14 +55,6 @@
| Docker Container ID |
|
-
- Docker User ID |
- |
-
-
- Docker Container Name |
- {{ $server->username }} |
-
Service |
diff --git a/resources/themes/pterodactyl/server/console.blade.php b/resources/themes/pterodactyl/server/console.blade.php
index 20356c272..f50db615f 100644
--- a/resources/themes/pterodactyl/server/console.blade.php
+++ b/resources/themes/pterodactyl/server/console.blade.php
@@ -16,7 +16,7 @@
diff --git a/resources/themes/pterodactyl/server/index.blade.php b/resources/themes/pterodactyl/server/index.blade.php
index 6470a7703..9cb4ba4a9 100644
--- a/resources/themes/pterodactyl/server/index.blade.php
+++ b/resources/themes/pterodactyl/server/index.blade.php
@@ -30,7 +30,7 @@
diff --git a/resources/themes/pterodactyl/server/settings/sftp.blade.php b/resources/themes/pterodactyl/server/settings/sftp.blade.php
index 3e00bdf57..5da21ef77 100644
--- a/resources/themes/pterodactyl/server/settings/sftp.blade.php
+++ b/resources/themes/pterodactyl/server/settings/sftp.blade.php
@@ -21,37 +21,7 @@
@section('content')
-
-
-
- @can('reset-sftp', $server)
-
- @else
-
-
- @lang('auth.not_authorized')
-
-
- @endcan
-
-
-
+
diff --git a/routes/api-remote.php b/routes/api-remote.php
index 28f1edb38..0aa42b1a2 100644
--- a/routes/api-remote.php
+++ b/routes/api-remote.php
@@ -12,3 +12,7 @@ Route::group(['prefix' => '/eggs'], function () {
Route::get('/', 'EggRetrievalController@index')->name('api.remote.eggs');
Route::get('/{uuid}', 'EggRetrievalController@download')->name('api.remote.eggs.download');
});
+
+Route::group(['prefix' => '/sftp'], function () {
+ Route::post('/', 'SftpController@index')->name('api.remote.sftp');
+});
diff --git a/routes/server.php b/routes/server.php
index fc658b673..1ceafbe87 100644
--- a/routes/server.php
+++ b/routes/server.php
@@ -21,10 +21,9 @@ Route::group(['prefix' => 'settings'], function () {
Route::get('/allocation', 'Settings\AllocationController@index')->name('server.settings.allocation');
Route::patch('/allocation', 'Settings\AllocationController@update');
- Route::get('/sftp', 'ServerController@getSFTP')->name('server.settings.sftp');
- Route::get('/startup', 'ServerController@getStartup')->name('server.settings.startup');
+ Route::get('/sftp', 'Settings\SftpController@index')->name('server.settings.sftp');
- Route::post('/sftp', 'ServerController@postSettingsSFTP');
+ Route::get('/startup', 'ServerController@getStartup')->name('server.settings.startup');
Route::post('/startup', 'ServerController@postSettingsStartup');
});
diff --git a/tests/Unit/Services/Servers/ServerCreationServiceTest.php b/tests/Unit/Services/Servers/ServerCreationServiceTest.php
index da2e33af2..89e67d916 100644
--- a/tests/Unit/Services/Servers/ServerCreationServiceTest.php
+++ b/tests/Unit/Services/Servers/ServerCreationServiceTest.php
@@ -18,7 +18,6 @@ use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Services\Servers\ServerCreationService;
use Pterodactyl\Services\Servers\VariableValidatorService;
-use Pterodactyl\Services\Servers\UsernameGenerationService;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
@@ -109,11 +108,6 @@ class ServerCreationServiceTest extends TestCase
*/
protected $userRepository;
- /**
- * @var \Pterodactyl\Services\Servers\UsernameGenerationService|\Mockery\Mock
- */
- protected $usernameService;
-
/**
* @var \Pterodactyl\Services\Servers\VariableValidatorService|\Mockery\Mock
*/
@@ -135,7 +129,6 @@ class ServerCreationServiceTest extends TestCase
$this->repository = m::mock(ServerRepositoryInterface::class);
$this->serverVariableRepository = m::mock(ServerVariableRepositoryInterface::class);
$this->userRepository = m::mock(UserRepositoryInterface::class);
- $this->usernameService = m::mock(UsernameGenerationService::class);
$this->validatorService = m::mock(VariableValidatorService::class);
$this->getFunctionMock('\\Pterodactyl\\Services\\Servers', 'str_random')
@@ -150,7 +143,6 @@ class ServerCreationServiceTest extends TestCase
$this->repository,
$this->serverVariableRepository,
$this->userRepository,
- $this->usernameService,
$this->validatorService
);
}
@@ -165,8 +157,6 @@ class ServerCreationServiceTest extends TestCase
->shouldReceive('validate')->with($this->data['egg_id'])->once()->andReturnSelf();
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->usernameService->shouldReceive('generate')->with($this->data['name'], 'random_string')
- ->once()->andReturn('user_name');
$this->repository->shouldReceive('create')->with(m::subset([
'uuid' => $this->getKnownUuid(),
@@ -211,7 +201,6 @@ class ServerCreationServiceTest extends TestCase
{
$this->validatorService->shouldReceive('isAdmin->setFields->validate->getResults')->once()->andReturn([]);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->usernameService->shouldReceive('generate')->once()->andReturn('user_name');
$this->repository->shouldReceive('create')->once()->andReturn((object) [
'node_id' => 1,
'id' => 1,
diff --git a/tests/Unit/Services/Servers/UsernameGenerationServiceTest.php b/tests/Unit/Services/Servers/UsernameGenerationServiceTest.php
deleted file mode 100644
index 61c364338..000000000
--- a/tests/Unit/Services/Servers/UsernameGenerationServiceTest.php
+++ /dev/null
@@ -1,109 +0,0 @@
-.
- *
- * This software is licensed under the terms of the MIT license.
- * https://opensource.org/licenses/MIT
- */
-
-namespace Tests\Unit\Services\Servers;
-
-use Tests\TestCase;
-use phpmock\phpunit\PHPMock;
-use Pterodactyl\Services\Servers\UsernameGenerationService;
-
-class UsernameGenerationServiceTest extends TestCase
-{
- use PHPMock;
-
- /**
- * @var UsernameGenerationService
- */
- protected $service;
-
- /**
- * Setup tests.
- */
- public function setUp()
- {
- parent::setUp();
-
- $this->service = new UsernameGenerationService();
-
- $this->getFunctionMock('\\Pterodactyl\\Services\\Servers', 'str_random')
- ->expects($this->any())->willReturnCallback(function ($count) {
- return str_pad('', $count, '0');
- });
- }
-
- /**
- * Test that a valid username is returned and is the correct length.
- */
- public function testShouldReturnAValidUsernameWithASelfGeneratedIdentifier()
- {
- $response = $this->service->generate('testname');
-
- $this->assertEquals('testna_00000000', $response);
- }
-
- /**
- * Test that a name and identifier provided returns the expected username.
- */
- public function testShouldReturnAValidUsernameWithAnIdentifierProvided()
- {
- $response = $this->service->generate('testname', 'identifier');
-
- $this->assertEquals('testna_identifi', $response);
- }
-
- /**
- * Test that the identifier is extended to 8 characters if it is shorter.
- */
- public function testShouldExtendIdentifierToBe8CharactersIfItIsShorter()
- {
- $response = $this->service->generate('testname', 'xyz');
-
- $this->assertEquals('testna_xyz00000', $response);
- }
-
- /**
- * Test that special characters are removed from the username.
- */
- public function testShouldStripSpecialCharactersFromName()
- {
- $response = $this->service->generate('te!st_n$ame', 'identifier');
-
- $this->assertEquals('testna_identifi', $response);
- }
-
- /**
- * Test that an empty name is replaced with 6 random characters.
- */
- public function testEmptyNamesShouldBeReplacedWithRandomCharacters()
- {
- $response = $this->service->generate('');
-
- $this->assertEquals('000000_00000000', $response);
- }
-
- /**
- * Test that a name consisting entirely of special characters is handled.
- */
- public function testNameOfOnlySpecialCharactersIsHandledProperly()
- {
- $response = $this->service->generate('$%#*#(@#(#*$(#!#@');
-
- $this->assertEquals('000000_00000000', $response);
- }
-
- /**
- * Test that passing a name shorter than 6 characters returns the entire name.
- */
- public function testNameShorterThan6CharactersShouldBeRenderedEntirely()
- {
- $response = $this->service->generate('test', 'identifier');
-
- $this->assertEquals('test_identifi', $response);
- }
-}
diff --git a/tests/Unit/Services/Sftp/AuthenticateUsingPasswordServiceTest.php b/tests/Unit/Services/Sftp/AuthenticateUsingPasswordServiceTest.php
new file mode 100644
index 000000000..87ceccd07
--- /dev/null
+++ b/tests/Unit/Services/Sftp/AuthenticateUsingPasswordServiceTest.php
@@ -0,0 +1,181 @@
+keyProviderService = m::mock(DaemonKeyProviderService::class);
+ $this->repository = m::mock(ServerRepositoryInterface::class);
+ $this->userRepository = m::mock(UserRepositoryInterface::class);
+ }
+
+ /**
+ * Test that an account can be authenticated.
+ */
+ public function testNonAdminAccountIsAuthenticated()
+ {
+ $user = factory(User::class)->make(['root_admin' => 0]);
+ $server = factory(Server::class)->make(['node_id' => 1, 'owner_id' => $user->id]);
+
+ $this->userRepository->shouldReceive('withColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf();
+ $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user);
+
+ $this->repository->shouldReceive('withColumns')->with(['id', 'node_id', 'owner_id', 'uuid'])->once()->andReturnSelf();
+ $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server);
+
+ $this->keyProviderService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn('server_token');
+
+ $response = $this->getService()->handle($user->username, 'password', 1, $server->uuidShort);
+ $this->assertNotEmpty($response);
+ $this->assertArrayHasKey('server', $response);
+ $this->assertArrayHasKey('token', $response);
+ $this->assertSame($server->uuid, $response['server']);
+ $this->assertSame('server_token', $response['token']);
+ }
+
+ /**
+ * Test that an administrative user can access servers that they are not
+ * set as the owner of.
+ */
+ public function testAdminAccountIsAuthenticated()
+ {
+ $user = factory(User::class)->make(['root_admin' => 1]);
+ $server = factory(Server::class)->make(['node_id' => 1, 'owner_id' => $user->id + 1]);
+
+ $this->userRepository->shouldReceive('withColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf();
+ $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user);
+
+ $this->repository->shouldReceive('withColumns')->with(['id', 'node_id', 'owner_id', 'uuid'])->once()->andReturnSelf();
+ $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server);
+
+ $this->keyProviderService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn('server_token');
+
+ $response = $this->getService()->handle($user->username, 'password', 1, $server->uuidShort);
+ $this->assertNotEmpty($response);
+ $this->assertArrayHasKey('server', $response);
+ $this->assertArrayHasKey('token', $response);
+ $this->assertSame($server->uuid, $response['server']);
+ $this->assertSame('server_token', $response['token']);
+ }
+
+ /**
+ * Test exception gets thrown if no server is passed into the function.
+ *
+ * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException
+ */
+ public function testExceptionIsThrownIfNoServerIsProvided()
+ {
+ $this->getService()->handle('username', 'password', 1);
+ }
+
+ /**
+ * Test that an exception is thrown if the user account exists but the wrong
+ * credentials are passed.
+ *
+ * @expectedException \Illuminate\Auth\AuthenticationException
+ */
+ public function testExceptionIsThrownIfUserDetailsAreIncorrect()
+ {
+ $user = factory(User::class)->make();
+
+ $this->userRepository->shouldReceive('withColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf();
+ $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user);
+
+ $this->getService()->handle($user->username, 'wrongpassword', 1, '1234');
+ }
+
+ /**
+ * Test that an exception is thrown if no user account is found.
+ *
+ * @expectedException \Illuminate\Auth\AuthenticationException
+ */
+ public function testExceptionIsThrownIfNoUserAccountIsFound()
+ {
+ $this->userRepository->shouldReceive('withColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf();
+ $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', 'something']])->once()->andThrow(new RecordNotFoundException);
+
+ $this->getService()->handle('something', 'password', 1, '1234');
+ }
+
+ /**
+ * Test that an exception is thrown if the user is not the owner of the server
+ * and is not an administrator.
+ *
+ * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException
+ */
+ public function testExceptionIsThrownIfUserDoesNotOwnServer()
+ {
+ $user = factory(User::class)->make(['root_admin' => 0]);
+ $server = factory(Server::class)->make(['node_id' => 1, 'owner_id' => $user->id + 1]);
+
+ $this->userRepository->shouldReceive('withColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf();
+ $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user);
+
+ $this->repository->shouldReceive('withColumns')->with(['id', 'node_id', 'owner_id', 'uuid'])->once()->andReturnSelf();
+ $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server);
+
+ $this->getService()->handle($user->username, 'password', 1, $server->uuidShort);
+ }
+
+ /**
+ * Test that an exception is thrown if the requested server does not belong to
+ * the node that the request is made from.
+ *
+ * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException
+ */
+ public function testExceptionIsThrownIfServerDoesNotExistOnCurrentNode()
+ {
+ $user = factory(User::class)->make(['root_admin' => 0]);
+ $server = factory(Server::class)->make(['node_id' => 2, 'owner_id' => $user->id]);
+
+ $this->userRepository->shouldReceive('withColumns')->with(['id', 'root_admin', 'password'])->once()->andReturnSelf();
+ $this->userRepository->shouldReceive('findFirstWhere')->with([['username', '=', $user->username]])->once()->andReturn($user);
+
+ $this->repository->shouldReceive('withColumns')->with(['id', 'node_id', 'owner_id', 'uuid'])->once()->andReturnSelf();
+ $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server);
+
+ $this->getService()->handle($user->username, 'password', 1, $server->uuidShort);
+ }
+
+ /**
+ * Return an instance of the service with mocked dependencies.
+ *
+ * @return \Pterodactyl\Services\Sftp\AuthenticateUsingPasswordService
+ */
+ private function getService(): AuthenticateUsingPasswordService
+ {
+ return new AuthenticateUsingPasswordService($this->keyProviderService, $this->repository, $this->userRepository);
+ }
+}
|