From 12927a32022fceb1343afdc92cac9ca483b12817 Mon Sep 17 00:00:00 2001 From: DaneEveritt Date: Sun, 15 May 2022 15:37:58 -0400 Subject: [PATCH] Update SFTP authentication endpoint to support returning user public keys --- .../Remote/SftpAuthenticationController.php | 136 +++++++++++------- .../Remote/SftpAuthenticationFormRequest.php | 8 +- 2 files changed, 90 insertions(+), 54 deletions(-) diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index 4a04c3089..b139ab3cf 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -2,94 +2,133 @@ namespace Pterodactyl\Http\Controllers\Api\Remote; +use Pterodactyl\Models\User; use Illuminate\Http\Request; +use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Permission; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ThrottlesLogins; -use Pterodactyl\Repositories\Eloquent\UserRepository; use Pterodactyl\Exceptions\Http\HttpForbiddenException; -use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\GetUserPermissionsService; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; -class SftpAuthenticationController extends Controller +abstract class SftpAuthenticationController extends Controller { use ThrottlesLogins; - /** - * @var \Pterodactyl\Repositories\Eloquent\UserRepository - */ - private $userRepository; + protected GetUserPermissionsService $permissions; - /** - * @var \Pterodactyl\Repositories\Eloquent\ServerRepository - */ - private $serverRepository; - - /** - * @var \Pterodactyl\Services\Servers\GetUserPermissionsService - */ - private $permissionsService; - - /** - * SftpController constructor. - */ - public function __construct( - GetUserPermissionsService $permissionsService, - UserRepository $userRepository, - ServerRepository $serverRepository - ) { - $this->userRepository = $userRepository; - $this->serverRepository = $serverRepository; - $this->permissionsService = $permissionsService; + public function __construct(GetUserPermissionsService $permissions) + { + $this->permissions = $permissions; } /** * Authenticate a set of credentials and return the associated server details * for a SFTP connection on the daemon. - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse + { + $connection = $this->parseUsername($request->input('username')); + + $this->validateRequestState($request); + + $user = $this->getUser($request, $connection['username']); + $server = $this->getServer($request, $connection['server']); + + if ($request->input('type') !== 'public_key') { + if (!password_verify($request->input('password'), $user->password)) { + $this->reject($request); + } + } + + $this->validateSftpAccess($user, $server); + + return new JsonResponse([ + 'server' => $server->uuid, + 'public_keys' => $user->sshKeys->map(fn ($value) => $value->public_key)->toArray(), + 'permissions' => $permissions ?? ['*'], + ]); + } + + /** + * Finds the server being requested and ensures that it belongs to the node this + * request stems from. + */ + protected function getServer(Request $request, string $uuid): Server + { + return Server::query() + ->where(fn ($builder) => $builder->where('uuid', $uuid)->orWhere('uuidShort', $uuid)) + ->where('node_id', $request->attributes->get('node')->id) + ->firstOr(function () use ($request) { + $this->reject($request); + }); + } + + /** + * Finds a user with the given username or increments the login attempts. + */ + protected function getUser(Request $request, string $username): User + { + return User::query()->where('username', $username)->firstOr(function () use ($request) { + $this->reject($request); + }); + } + + /** + * Parses the username provided to the request. + * + * @return array{"username": string, "server": string} + */ + protected function parseUsername(string $value): array { // Reverse the string to avoid issues with usernames that contain periods. - $parts = explode('.', strrev($request->input('username')), 2); + $parts = explode('.', strrev($value), 2); // Unreverse the strings after parsing them apart. - $connection = [ + return [ 'username' => strrev(array_get($parts, 1)), 'server' => strrev(array_get($parts, 0)), ]; + } + /** + * Checks that the request should not be throttled yet, and that the server was + * provided in the username. + */ + protected function validateRequestState(Request $request): void + { if ($this->hasTooManyLoginAttempts($request)) { $seconds = $this->limiter()->availableIn($this->throttleKey($request)); throw new TooManyRequestsHttpException($seconds, "Too many login attempts for this account, please try again in {$seconds} seconds."); } - /** @var \Pterodactyl\Models\Node $node */ - $node = $request->attributes->get('node'); if (empty($connection['server'])) { throw new NotFoundHttpException(); } + } - /** @var \Pterodactyl\Models\User $user */ - $user = $this->userRepository->findFirstWhere([ - ['username', '=', $connection['username']], - ]); + /** + * Rejects the request and increments the login attempts. + */ + protected function reject(Request $request): void + { + $this->incrementLoginAttempts($request); - $server = $this->serverRepository->getByUuid($connection['server'] ?? ''); - if (!password_verify($request->input('password'), $user->password) || $server->node_id !== $node->id) { - $this->incrementLoginAttempts($request); - - throw new HttpForbiddenException('Authorization credentials were not correct, please try again.'); - } + throw new HttpForbiddenException('Authorization credentials were not correct, please try again.'); + } + /** + * Validates that a user should have permission to use SFTP for the given server. + */ + protected function validateSftpAccess(User $user, Server $server): void + { if (!$user->root_admin && $server->owner_id !== $user->id) { - $permissions = $this->permissionsService->handle($server, $user); + $permissions = $this->permissions->handle($server, $user); if (!in_array(Permission::ACTION_FILE_SFTP, $permissions)) { throw new HttpForbiddenException('You do not have permission to access SFTP for this server.'); @@ -97,13 +136,6 @@ class SftpAuthenticationController extends Controller } $server->validateCurrentState(); - - return new JsonResponse([ - 'server' => $server->uuid, - // Deprecated, but still needed at the moment for Wings. - 'token' => '', - 'permissions' => $permissions ?? ['*'], - ]); } /** diff --git a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php index 041ff197f..bb6ec5be8 100644 --- a/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php +++ b/app/Http/Requests/Api/Remote/SftpAuthenticationFormRequest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Requests\Api\Remote; +use Illuminate\Validation\Rule; use Illuminate\Foundation\Http\FormRequest; class SftpAuthenticationFormRequest extends FormRequest @@ -24,8 +25,11 @@ class SftpAuthenticationFormRequest extends FormRequest public function rules() { return [ - 'username' => 'required|string', - 'password' => 'required|string', + 'type' => ['nullable', 'in:password,public_key'], + 'username' => ['required', 'string'], + 'password' => [ + Rule::when(fn () => $this->input('type') !== 'public_key', ['required', 'string'], ['nullable']), + ], ]; }