diff --git a/app/Exceptions/Http/Server/ServerStateConflictException.php b/app/Exceptions/Http/Server/ServerStateConflictException.php new file mode 100644 index 000000000..f0eb096b1 --- /dev/null +++ b/app/Exceptions/Http/Server/ServerStateConflictException.php @@ -0,0 +1,30 @@ +isSuspended()) { + $message = 'This server is currently suspended and the functionality requested is unavailable.'; + } elseif (!$server->isInstalled()) { + $message = 'This server has not yet completed its installation process, please try again later.'; + } elseif ($server->status === Server::STATUS_RESTORING_BACKUP) { + $message = 'This server is currently restoring from a backup, please try again later.'; + } elseif (!is_null($server->transfer)) { + $message = 'This server is currently being transferred to a new machine, please try again later.'; + } + + parent::__construct($message, $previous); + } +} diff --git a/app/Exceptions/Http/Server/ServerTransferringException.php b/app/Exceptions/Http/Server/ServerTransferringException.php deleted file mode 100644 index d36fb8de4..000000000 --- a/app/Exceptions/Http/Server/ServerTransferringException.php +++ /dev/null @@ -1,17 +0,0 @@ -transfer)) { - throw new ServerTransferringException(); - } - - // Remember, for security purposes, only reveal the existence of the server to people that - // have provided valid credentials, and have permissions to know about it. - if ($server->isSuspended() || !$server->isInstalled()) { - throw new BadRequestHttpException('Server is not installed or is currently suspended.'); - } + $server->validateCurrentState(); return new JsonResponse([ 'server' => $server->uuid, diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 88df47e1f..d2e0f2cc7 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -28,7 +28,6 @@ use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess; use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; -use Pterodactyl\Http\Middleware\Server\AccessingValidServer; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; @@ -106,7 +105,6 @@ class Kernel extends HttpKernel 'auth' => Authenticate::class, 'auth.basic' => AuthenticateWithBasicAuth::class, 'guest' => RedirectIfAuthenticated::class, - 'server' => AccessingValidServer::class, 'admin' => AdminAuthenticate::class, 'csrf' => VerifyCsrfToken::class, 'throttle' => ThrottleRequests::class, diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 4ab87ce44..0634e2585 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -6,10 +6,8 @@ use Closure; use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Pterodactyl\Exceptions\Http\Server\ServerTransferringException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; class AuthenticateServerAccess { @@ -60,23 +58,17 @@ class AuthenticateServerAccess } } - if ($server->suspended && !$request->routeIs('api:client:server.resources')) { - throw new BadRequestHttpException('This server is currently suspended and the functionality requested is unavailable.'); - } - - // Still allow users to get information about their server if it is installing or being transferred. - if (!$request->routeIs('api:client:server.view')) { - if (!$server->isInstalled()) { - // Throw an exception for all server routes; however if the user is an admin and requesting the - // server details, don't throw the exception for them. - if (!$user->root_admin || ($user->root_admin && !$request->routeIs($this->except))) { - throw new ConflictHttpException('Server has not completed the installation process.'); + try { + $server->validateCurrentState(); + } catch (ServerStateConflictException $exception) { + // Still allow users to get information about their server if it is installing or + // being transferred. + if (!$request->routeIs('api:client:server.view')) { + if ($server->isSuspended() && !$request->routeIs('api:client:server.resources')) { + throw $exception; } - } - - if (!is_null($server->transfer)) { - if (!$user->root_admin || ($user->root_admin && !$request->routeIs($this->except))) { - throw new ServerTransferringException(); + if (!$user->root_admin || !$request->routeIs($this->except)) { + throw $exception; } } } diff --git a/app/Http/Middleware/Server/AccessingValidServer.php b/app/Http/Middleware/Server/AccessingValidServer.php deleted file mode 100644 index 441c74b52..000000000 --- a/app/Http/Middleware/Server/AccessingValidServer.php +++ /dev/null @@ -1,92 +0,0 @@ -config = $config; - $this->repository = $repository; - $this->response = $response; - } - - /** - * Determine if a given user has permission to access a server. - * - * @return \Illuminate\Http\Response|mixed - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function handle(Request $request, Closure $next) - { - $attributes = $request->route()->parameter('server'); - $isApiRequest = $request->expectsJson() || $request->is(...$this->config->get('pterodactyl.json_routes', [])); - $server = $this->repository->getByUuid($attributes instanceof Server ? $attributes->uuid : $attributes); - - if ($server->isSuspended()) { - if ($isApiRequest) { - throw new AccessDeniedHttpException('Server is suspended and cannot be accessed.'); - } - - return $this->response->view('errors.suspended', [], 403); - } - - // Servers can have install statuses other than 1 or 0, so don't check - // for a bool-type operator here. - if (!$server->isInstalled()) { - if ($isApiRequest) { - throw new ConflictHttpException('Server is still completing the installation process.'); - } - - return $this->response->view('errors.installing', [], 409); - } - - if (!is_null($server->transfer)) { - if ($isApiRequest) { - throw new ServerTransferringException(); - } - - return $this->response->view('errors.transferring', [], 409); - } - - // Add server to the request attributes. This will replace sessions - // as files are updated. - $request->attributes->set('server', $server); - - return $next($request); - } -} diff --git a/app/Models/Server.php b/app/Models/Server.php index 68f8572d3..9aa4b618d 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -6,6 +6,7 @@ use Closure; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Query\JoinClause; use Znck\Eloquent\Traits\BelongsToThrough; +use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; /** * @property int $id @@ -371,4 +372,23 @@ class Server extends Model { return $this->hasMany(AuditLog::class); } + + /** + * Checks if the server is currently in a user-accessible state. If not, an + * exception is raised. This should be called whenever something needs to make + * sure the server is not in a weird state that should block user access. + * + * @throws \Pterodactyl\Exceptions\Http\Server\ServerStateConflictException + */ + public function validateCurrentState() + { + if ( + $this->isSuspended() || + !$this->isInstalled() || + $this->status === self::STATUS_RESTORING_BACKUP || + !is_null($this->transfer) + ) { + throw new ServerStateConflictException($this); + } + } } diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index 6b1c15541..3c61b1935 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -6,7 +6,7 @@ use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Repositories\Wings\DaemonServerRepository; -use Pterodactyl\Exceptions\Http\Server\ServerTransferringException; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; class SuspensionService { @@ -55,7 +55,7 @@ class SuspensionService // Check if the server is currently being transferred. if (!is_null($server->transfer)) { - throw new ServerTransferringException(); + throw new ConflictHttpException('Cannot toggle suspension status on a server that is currently being transferred.'); } $this->connection->transaction(function () use ($action, $server, $isSuspending) { diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index 017f933b2..40eebefac 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -111,21 +111,23 @@ export default ({ server, className }: { server: Server; className?: string }) = : - server.isInstalling ? + (server.isTransferring || server.status) ?
- Installing + {server.isTransferring ? + 'Transferring' + : + server.status === 'installing' ? 'Installing' : ( + server.status === 'restoring_backup' ? + 'Restoring Backup' + : + 'Unavailable' + ) + }
: - server.isTransferring ? -
- - Transferring - -
- : - + :