From e6c4a68e4a0974bb19f59bcad2337d0dadd37d17 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 16 Dec 2020 09:34:47 -0700 Subject: [PATCH 01/14] Update logic for tracking a server's transfer state --- .../Servers/ServerTransferController.php | 2 +- .../Servers/ServerTransferController.php | 27 +++++---- .../Remote/SftpAuthenticationController.php | 4 +- .../Server/AuthenticateServerAccess.php | 23 +++++--- .../Server/AccessingValidServer.php | 8 +++ app/Models/Server.php | 2 +- app/Models/ServerTransfer.php | 4 +- .../Servers/GetUserPermissionsService.php | 1 + app/Services/Servers/SuspensionService.php | 5 +- .../Api/Application/ServerTransformer.php | 3 +- .../Api/Client/ServerTransformer.php | 1 + ...uccessful_nullable_in_server_transfers.php | 32 ++++++++++ resources/scripts/api/server/getServer.ts | 2 + .../components/dashboard/ServerRow.tsx | 23 +++++--- .../components/elements/ProgressBar.tsx | 2 +- .../scripts/components/server/Console.tsx | 30 ++++++++-- .../components/server/ServerConsole.tsx | 21 +++++-- .../components/server/ServerDetailsBlock.tsx | 13 ++-- resources/scripts/routers/ServerRouter.tsx | 18 +++++- .../views/admin/servers/view/manage.blade.php | 59 +++++++++++++------ 20 files changed, 206 insertions(+), 74 deletions(-) create mode 100644 database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php index 323ffb7bc..62971bd99 100644 --- a/app/Http/Controllers/Admin/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -117,7 +117,7 @@ class ServerTransferController extends Controller $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(); // Suspend the server and request an archive to be created. - $this->suspensionService->toggle($server, 'suspend'); + //$this->suspensionService->toggle($server, 'suspend'); // Create a new ServerTransfer entry. $transfer = new ServerTransfer; diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index bd8827a6e..20f396805 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -120,9 +120,12 @@ class ServerTransferController extends Controller // Unsuspend the server and don't continue the transfer. if (! $request->input('successful')) { - $this->suspensionService->toggle($server, 'unsuspend'); + //$this->suspensionService->toggle($server, 'unsuspend'); + $server->transfer->forceFill([ + 'successful' => false, + ])->saveOrFail(); - return JsonResponse::create([], Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } $server->node_id = $server->transfer->new_node; @@ -151,21 +154,23 @@ class ServerTransferController extends Controller // because setServer() tells the repository to use the server's node and not the one // we want to specify. try { + /** @var \Pterodactyl\Models\Node $newNode */ + $newNode = $this->nodeRepository->find($server->transfer->new_node); + $this->daemonTransferRepository ->setServer($server) - ->setNode($this->nodeRepository->find($server->transfer->new_node)) + ->setNode($newNode) ->notify($server, $data, $server->node, $token->__toString()); } catch (DaemonConnectionException $exception) { throw $exception; } - return JsonResponse::create([], Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** * The daemon notifies us about a transfer failure. * - * @param \Illuminate\Http\Request $request * @param string $uuid * @return \Illuminate\Http\JsonResponse * @@ -183,9 +188,9 @@ class ServerTransferController extends Controller $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); // Unsuspend the server. - $this->suspensionService->toggle($server, 'unsuspend'); + //$this->suspensionService->toggle($server, 'unsuspend'); - return JsonResponse::create([], Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** @@ -213,11 +218,11 @@ class ServerTransferController extends Controller // Update the server's allocation_id and node_id. $server->allocation_id = $transfer->new_allocation; $server->node_id = $transfer->new_node; - $server->save(); + $server->saveOrFail(); // Mark the transfer as successful. $transfer->successful = true; - $transfer->save(); + $transfer->saveOrFail(); // Commit the transaction. $this->connection->commit(); @@ -231,8 +236,8 @@ class ServerTransferController extends Controller // Unsuspend the server $server->load('node'); - $this->suspensionService->toggle($server, $this->suspensionService::ACTION_UNSUSPEND); + //$this->suspensionService->toggle($server, $this->suspensionService::ACTION_UNSUSPEND); - return JsonResponse::create([], Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index b62d6e5c1..7fb5625f4 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -112,13 +112,13 @@ class SftpAuthenticationController extends Controller // Remeber, 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->installed !== 1 || $server->suspended) { + if ($server->installed !== 1 || $server->suspended || $server->transfer !== null) { throw new BadRequestHttpException( 'Server is not installed or is currently suspended.' ); } - return JsonResponse::create([ + return new JsonResponse([ 'server' => $server->uuid, // Deprecated, but still needed at the moment for Wings. 'token' => '', diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 505f1a305..eee798198 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -9,7 +9,6 @@ use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class AuthenticateServerAccess { @@ -24,7 +23,6 @@ class AuthenticateServerAccess * @var string[] */ protected $except = [ - 'api:client:server.view', 'api:client:server.ws', ]; @@ -65,17 +63,26 @@ class AuthenticateServerAccess } } - if ($server->suspended && !$request->routeIs('api:client:server.resources')) { + if ($server->suspended && ! $request->routeIs('api:client:server.resources')) { throw new BadRequestHttpException( 'This server is currently suspended and the functionality requested is unavailable.' ); } - 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.'); + // Still allow users to get information about there 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.'); + } + } + + if ($server->transfer !== null) { + if (! $user->root_admin || ($user->root_admin && ! $request->routeIs($this->except))) { + throw new ConflictHttpException('Server is currently being transferred.'); + } } } diff --git a/app/Http/Middleware/Server/AccessingValidServer.php b/app/Http/Middleware/Server/AccessingValidServer.php index 1f464f7df..1bed5f556 100644 --- a/app/Http/Middleware/Server/AccessingValidServer.php +++ b/app/Http/Middleware/Server/AccessingValidServer.php @@ -80,6 +80,14 @@ class AccessingValidServer return $this->response->view('errors.installing', [], 409); } + if ($server->transfer !== null) { + if ($isApiRequest) { + throw new ConflictHttpException('Server is currently being transferred.'); + } + + 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); diff --git a/app/Models/Server.php b/app/Models/Server.php index c8b8615fe..aace86d0b 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -306,7 +306,7 @@ class Server extends Model */ public function transfer() { - return $this->hasOne(ServerTransfer::class)->orderByDesc('id'); + return $this->hasOne(ServerTransfer::class)->whereNull('successful')->orderByDesc('id'); } /** diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php index 8d7120645..48f6b8de8 100644 --- a/app/Models/ServerTransfer.php +++ b/app/Models/ServerTransfer.php @@ -11,7 +11,7 @@ namespace Pterodactyl\Models; * @property int $new_allocation * @property string $old_additional_allocations * @property string $new_additional_allocations - * @property bool $successful + * @property bool|null $successful * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @@ -66,7 +66,7 @@ class ServerTransfer extends Model 'new_allocation' => 'required|numeric', 'old_additional_allocations' => 'nullable', 'new_additional_allocations' => 'nullable', - 'successful' => 'sometimes|boolean', + 'successful' => 'sometimes|nullable|boolean', ]; /** diff --git a/app/Services/Servers/GetUserPermissionsService.php b/app/Services/Servers/GetUserPermissionsService.php index e0ea20373..80ce35b52 100644 --- a/app/Services/Servers/GetUserPermissionsService.php +++ b/app/Services/Servers/GetUserPermissionsService.php @@ -24,6 +24,7 @@ class GetUserPermissionsService if ($user->root_admin) { $permissions[] = 'admin.websocket.errors'; $permissions[] = 'admin.websocket.install'; + $permissions[] = 'admin.websocket.transfer'; } return $permissions; diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index 6497d73ed..200ef9b65 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -61,7 +61,10 @@ class SuspensionService 'suspended' => $action === self::ACTION_SUSPEND, ]); - $this->daemonServerRepository->setServer($server)->suspend($action === self::ACTION_UNSUSPEND); + // Only send the suspension request to wings if the server is not currently being transferred. + if ($server->transfer === null) { + $this->daemonServerRepository->setServer($server)->suspend($action === self::ACTION_UNSUSPEND); + } }); } } diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index e2c32eb76..10c343d8c 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -28,6 +28,7 @@ class ServerTransformer extends BaseTransformer 'location', 'node', 'databases', + 'transfer', ]; /** @@ -55,8 +56,6 @@ class ServerTransformer extends BaseTransformer * * @param \Pterodactyl\Models\Server $server * @return array - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function transform(Server $server): array { diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 9897f8517..766b3b726 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -72,6 +72,7 @@ class ServerTransformer extends BaseClientTransformer ], 'is_suspended' => $server->suspended, 'is_installing' => $server->installed !== 1, + 'is_transferring' => $server->transfer !== null, ]; } diff --git a/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php b/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php new file mode 100644 index 000000000..0a2885284 --- /dev/null +++ b/database/migrations/2020_12_14_013707_make_successful_nullable_in_server_transfers.php @@ -0,0 +1,32 @@ +boolean('successful')->nullable()->default(null)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('server_transfers', function (Blueprint $table) { + $table->boolean('successful')->default(0)->change(); + }); + } +} diff --git a/resources/scripts/api/server/getServer.ts b/resources/scripts/api/server/getServer.ts index d9b76b400..959149f9d 100644 --- a/resources/scripts/api/server/getServer.ts +++ b/resources/scripts/api/server/getServer.ts @@ -40,6 +40,7 @@ export interface Server { }; isSuspended: boolean; isInstalling: boolean; + isTransferring: boolean; variables: ServerEggVariable[]; allocations: Allocation[]; } @@ -62,6 +63,7 @@ export const rawDataToServerObject = ({ attributes: data }: FractalResponseData) featureLimits: { ...data.feature_limits }, isSuspended: data.is_suspended, isInstalling: data.is_installing, + isTransferring: data.is_transferring, variables: ((data.relationships?.variables as FractalResponseList | undefined)?.data || []).map(rawDataToServerEggVariable), allocations: ((data.relationships?.allocations as FractalResponseList | undefined)?.data || []).map(rawDataToServerAllocation), }); diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index d42b3e434..7db977311 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -26,14 +26,14 @@ const IconDescription = styled.p<{ $alarm: boolean }>` const StatusIndicatorBox = styled(GreyRowBox)<{ $status: ServerPowerState | undefined }>` ${tw`grid grid-cols-12 gap-4 relative`}; - + & .status-bar { ${tw`w-2 bg-red-500 absolute right-0 z-20 rounded-full m-1 opacity-50 transition-all duration-150`}; height: calc(100% - 0.5rem); - + ${({ $status }) => (!$status || $status === 'offline') ? tw`bg-red-500` : ($status === 'running' ? tw`bg-green-500` : tw`bg-yellow-500`)}; } - + &:hover .status-bar { ${tw`opacity-75`}; } @@ -74,8 +74,8 @@ export default ({ server, className }: { server: Server; className?: string }) = alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk); } - const disklimit = server.limits.disk !== 0 ? megabytesToHuman(server.limits.disk) : 'Unlimited'; - const memorylimit = server.limits.memory !== 0 ? megabytesToHuman(server.limits.memory) : 'Unlimited'; + const diskLimit = server.limits.disk !== 0 ? megabytesToHuman(server.limits.disk) : 'Unlimited'; + const memoryLimit = server.limits.memory !== 0 ? megabytesToHuman(server.limits.memory) : 'Unlimited'; return ( @@ -118,7 +118,14 @@ export default ({ server, className }: { server: Server; className?: string }) = : - + server.isTransferring ? +
+ + Transferring + +
+ : + : -

of {memorylimit}

+

of {memoryLimit}

@@ -143,7 +150,7 @@ export default ({ server, className }: { server: Server; className?: string }) = {bytesToHuman(stats.diskUsageInBytes)}
-

of {disklimit}

+

of {diskLimit}

} diff --git a/resources/scripts/components/elements/ProgressBar.tsx b/resources/scripts/components/elements/ProgressBar.tsx index 64ee732b7..fe98579bd 100644 --- a/resources/scripts/components/elements/ProgressBar.tsx +++ b/resources/scripts/components/elements/ProgressBar.tsx @@ -59,7 +59,7 @@ export default () => { }, [ progress, continuous ]); return ( -
+
{ (prelude ? TERMINAL_PRELUDE : '') + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m', ); + const handleTransferStatus = (status: string) => { + switch (status) { + // Sent by either the source or target node if a failure occurs. + case 'failure': + terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m'); + return; + + // Sent by the source node whenever the server was archived successfully. + case 'archive': + terminal.writeln(TERMINAL_PRELUDE + 'Server has been archived successfully, attempting connection to target node..\u001b[0m'); + // TODO: Get WebSocket information for the target node. + return; + } + }; + const handleDaemonErrorOutput = (line: string) => terminal.writeln( TERMINAL_PRELUDE + '\u001b[1m\u001b[41m' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m', ); @@ -122,20 +137,23 @@ export default () => { // Add support for capturing keys terminal.attachCustomKeyEventHandler((e: KeyboardEvent) => { - // Ctrl + C ( Copy ) + // Ctrl + C (Copy) if (e.ctrlKey && e.key === 'c') { document.execCommand('copy'); return false; } + // Ctrl + F (Find) if (e.ctrlKey && e.key === 'f') { searchBar.show(); return false; } + // Escape if (e.key === 'Escape') { searchBar.hidden(); } + return true; }); } @@ -154,17 +172,21 @@ export default () => { instance.addListener('status', handlePowerChangeEvent); instance.addListener('console output', handleConsoleOutput); instance.addListener('install output', handleConsoleOutput); + instance.addListener('transfer logs', handleConsoleOutput); + instance.addListener('transfer status', handleTransferStatus); instance.addListener('daemon message', line => handleConsoleOutput(line, true)); instance.addListener('daemon error', handleDaemonErrorOutput); instance.send('send logs'); } return () => { - instance && instance.removeListener('console output', handleConsoleOutput) + instance && instance.removeListener('status', handlePowerChangeEvent) + .removeListener('console output', handleConsoleOutput) .removeListener('install output', handleConsoleOutput) + .removeListener('transfer logs', handleConsoleOutput) + .removeListener('transfer status', handleTransferStatus) .removeListener('daemon message', line => handleConsoleOutput(line, true)) - .removeListener('daemon error', handleDaemonErrorOutput) - .removeListener('status', handlePowerChangeEvent); + .removeListener('daemon error', handleDaemonErrorOutput); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [ connected, instance ]); diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 4735488fc..d090e7a95 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -17,6 +17,7 @@ const ChunkedStatGraphs = lazy(() => import(/* webpackChunkName: "graphs" */'@/c const ServerConsole = () => { const isInstalling = ServerContext.useStoreState(state => state.server.data!.isInstalling); + const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring); // @ts-ignore const eggFeatures: string[] = ServerContext.useStoreState(state => state.server.data!.eggFeatures, isEqual); @@ -24,11 +25,7 @@ const ServerConsole = () => {
- {!isInstalling ? - - - - : + {isInstalling ?

@@ -37,6 +34,20 @@ const ServerConsole = () => {

+ : + isTransferring ? +
+ +

+ This server is currently being transferred to another node and all actions + are unavailable. +

+
+
+ : + + + }
diff --git a/resources/scripts/components/server/ServerDetailsBlock.tsx b/resources/scripts/components/server/ServerDetailsBlock.tsx index 0d87089c1..11f41f690 100644 --- a/resources/scripts/components/server/ServerDetailsBlock.tsx +++ b/resources/scripts/components/server/ServerDetailsBlock.tsx @@ -65,13 +65,14 @@ const ServerDetailsBlock = () => { const name = ServerContext.useStoreState(state => state.server.data!.name); const isInstalling = ServerContext.useStoreState(state => state.server.data!.isInstalling); + const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring); const limits = ServerContext.useStoreState(state => state.server.data!.limits); const primaryAllocation = ServerContext.useStoreState(state => state.server.data!.allocations.filter(alloc => alloc.isDefault).map( allocation => (allocation.alias || allocation.ip) + ':' + allocation.port )).toString(); - const disklimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited'; - const memorylimit = limits.memory ? megabytesToHuman(limits.memory) : 'Unlimited'; + const diskLimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited'; + const memoryLimit = limits.memory ? megabytesToHuman(limits.memory) : 'Unlimited'; return ( @@ -81,10 +82,10 @@ const ServerDetailsBlock = () => { fixedWidth css={[ tw`mr-1`, - statusToColor(status, isInstalling), + statusToColor(status, isInstalling || isTransferring), ]} /> -  {!status ? 'Connecting...' : (isInstalling ? 'Installing' : status)} +  {!status ? 'Connecting...' : (isInstalling ? 'Installing' : (isTransferring) ? 'Transferring' : status)}

@@ -97,11 +98,11 @@ const ServerDetailsBlock = () => {

{bytesToHuman(stats.memory)} - / {memorylimit} + / {memoryLimit}

 {bytesToHuman(stats.disk)} - / {disklimit} + / {diskLimit}

); diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 0a09774aa..7405a6e4e 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -35,10 +35,12 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) const rootAdmin = useStoreState(state => state.user.data!.rootAdmin); const [ error, setError ] = useState(''); const [ installing, setInstalling ] = useState(false); + const [ transferring, setTransferring ] = useState(false); const id = ServerContext.useStoreState(state => state.server.data?.id); const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); const isInstalling = ServerContext.useStoreState(state => state.server.data?.isInstalling); + const isTransferring = ServerContext.useStoreState(state => state.server.data?.isTransferring); const serverId = ServerContext.useStoreState(state => state.server.data?.internalId); const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); const clearServerState = ServerContext.useStoreActions(actions => actions.clearServerState); @@ -51,13 +53,23 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) setInstalling(!!isInstalling); }, [ isInstalling ]); + useEffect(() => { + setTransferring(!!isTransferring); + }, [ isTransferring ]); + useEffect(() => { setError(''); setInstalling(false); + setTransferring(false); + getServer(match.params.id) .catch(error => { if (error.response?.status === 409) { - setInstalling(true); + if (error.response.data?.errors[0]?.detail?.includes('transfer')) { + setTransferring(true); + } else { + setInstalling(true); + } } else { console.error(error); setError(httpErrorToHuman(error)); @@ -117,9 +129,9 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) - {(installing && (!rootAdmin || (rootAdmin && !location.pathname.endsWith(`/server/${id}`)))) ? + {((installing || transferring) && (!rootAdmin || (rootAdmin && !location.pathname.endsWith(`/server/${id}`)))) ? diff --git a/resources/views/admin/servers/view/manage.blade.php b/resources/views/admin/servers/view/manage.blade.php index 93d7b6390..7ddff9a06 100644 --- a/resources/views/admin/servers/view/manage.blade.php +++ b/resources/views/admin/servers/view/manage.blade.php @@ -58,6 +58,7 @@
+ @if(! $server->suspended)
@@ -96,28 +97,48 @@
@endif -
-
-
-

Transfer Server

-
-
-

- Transfer this server to another node connected to this panel. - Warning! This feature has not been fully tested and may have bugs. -

-
+ @if($server->transfer === null) +
+
+
+

Transfer Server

+
+
+

+ Transfer this server to another node connected to this panel. + Warning! This feature has not been fully tested and may have bugs. +

+
-
-
+ @else +
+
+
+

Transfer Server

+
+
+

+ This server is currently being transferred to another node. + Transfer was initiated at {{ $server->transfer->created_at }} +

+
+ + +
+
+ @endif
From 01926e2896b038f304ed0c137d28f82a6c86d9be Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 16 Dec 2020 18:54:01 -0700 Subject: [PATCH 03/14] Improve logic for logging into the websocket of the target node --- .../Client/Servers/WebsocketController.php | 13 ++++--- .../Servers/ServerTransferController.php | 7 +++- app/Models/ServerTransfer.php | 2 + ...chived_field_to_server_transfers_table.php | 38 +++++++++++++++++++ .../scripts/api/server/getWebsocketToken.ts | 8 +--- .../components/server/WebsocketHandler.tsx | 26 ++++--------- 6 files changed, 63 insertions(+), 31 deletions(-) create mode 100644 database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php diff --git a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php index 5aaf28afd..f81bb227f 100644 --- a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php +++ b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php @@ -61,15 +61,18 @@ class WebsocketController extends ClientApiController $permissions = $this->permissionsService->handle($server, $user); $node = null; - - // Check if there is a transfer query param asking to connect to the target node's websocket. - if ($request->query('transfer', 'false') === 'true') { + if ($server->transfer !== null) { // Check if the user has permissions to receive transfer logs. if (! in_array('admin.websocket.transfer', $permissions)) { - throw new HttpException(Response::HTTP_FORBIDDEN, 'You do not have permission to get transfer logs'); + throw new HttpException(Response::HTTP_FORBIDDEN, 'You do not have permission to view transfer logs'); } - $node = $server->transfer->newNode; + // Redirect the websocket request to the new node if the server has been archived. + if ($server->transfer->archived) { + $node = $server->transfer->newNode; + } else { + $node = $server->node; + } } else { $node = $server->node; } diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index 388a1b5b3..8ee2c242f 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -112,7 +112,6 @@ class ServerTransferController extends Controller // Unsuspend the server and don't continue the transfer. if (! $request->input('successful')) { - //$this->suspensionService->toggle($server, 'unsuspend'); $server->transfer->forceFill([ 'successful' => false, ])->saveOrFail(); @@ -142,6 +141,12 @@ class ServerTransferController extends Controller ->relatedTo($server->uuid, true) ->getToken($signer, new Key($server->node->getDecryptedKey())); + // Update the archived field on the transfer to make clients connect to the websocket + // on the new node to be able to receive transfer logs. + $server->transfer->forceFill([ + 'archived' => true, + ])->saveOrFail(); + // On the daemon transfer repository, make sure to set the node after the server // because setServer() tells the repository to use the server's node and not the one // we want to specify. diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php index e7bde02f9..cb95fefc7 100644 --- a/app/Models/ServerTransfer.php +++ b/app/Models/ServerTransfer.php @@ -12,6 +12,7 @@ namespace Pterodactyl\Models; * @property string $old_additional_allocations * @property string $new_additional_allocations * @property bool|null $successful + * @property bool $archived * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @@ -55,6 +56,7 @@ class ServerTransfer extends Model 'old_additional_allocations' => 'string', 'new_additional_allocations' => 'string', 'successful' => 'bool', + 'archived' => 'bool', ]; /** diff --git a/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php b/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php new file mode 100644 index 000000000..1162d8a4f --- /dev/null +++ b/database/migrations/2020_12_17_014330_add_archived_field_to_server_transfers_table.php @@ -0,0 +1,38 @@ +boolean('archived')->default(0)->after('new_additional_allocations'); + }); + + // Update archived to all be true on existing transfers. + Schema::table('server_transfers', function (Blueprint $table) { + DB::statement('UPDATE `server_transfers` SET `archived` = 1 WHERE `successful` = 1'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('server_transfers', function (Blueprint $table) { + $table->dropColumn('archived'); + }); + } +} diff --git a/resources/scripts/api/server/getWebsocketToken.ts b/resources/scripts/api/server/getWebsocketToken.ts index d1e1f24a9..da78dfd05 100644 --- a/resources/scripts/api/server/getWebsocketToken.ts +++ b/resources/scripts/api/server/getWebsocketToken.ts @@ -5,13 +5,9 @@ interface Response { socket: string; } -export default (server: string, transfer: boolean): Promise => { +export default (server: string): Promise => { return new Promise((resolve, reject) => { - http.get(`/api/client/servers/${server}/websocket`, { - params: { - transfer, - }, - }) + http.get(`/api/client/servers/${server}/websocket`) .then(({ data }) => resolve({ token: data.data.token, socket: data.data.socket, diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index 085cd40f3..d519da4f5 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -15,15 +15,12 @@ const reconnectErrors = [ export default () => { let updatingToken = false; const [ error, setError ] = useState<'connecting' | string>(''); - const [ transfer, setTransfer ] = useState(false); const { connected, instance } = ServerContext.useStoreState(state => state.socket); const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); const setServerStatus = ServerContext.useStoreActions(actions => actions.status.setServerStatus); const { setInstance, setConnectionState } = ServerContext.useStoreActions(actions => actions.socket); - const connect = (uuid: string, transfer = false) => { - setTransfer(transfer); - + const connect = (uuid: string) => { const socket = new Websocket(); socket.on('auth success', () => setConnectionState(true)); @@ -52,21 +49,12 @@ export default () => { }); socket.on('transfer status', (status: string) => { - if (status === 'success') { - setTransfer(false); + if (status === 'starting' || status === 'success') { return; } - if (status === 'starting') { - return; - } - - // This doesn't use the `setTransfer` hook as it doesn't want to work properly in this context, - // and causes all kinds of fuckery with the websocket. - let transfer = false; - if (status === 'archived') { - transfer = true; - } + // Force a reconnection to the websocket which will connect us + // to the target node instead of the source node. // Close the current websocket connection. socket.close(); @@ -75,10 +63,10 @@ export default () => { setConnectionState(false); setInstance(null); - connect(uuid, transfer); + connect(uuid); }); - getWebsocketToken(uuid, transfer) + getWebsocketToken(uuid) .then(data => { // Connect and then set the authentication token. socket.setToken(data.token).connect(data.socket); @@ -93,7 +81,7 @@ export default () => { if (updatingToken) return; updatingToken = true; - getWebsocketToken(uuid, transfer) + getWebsocketToken(uuid) .then(data => socket.setToken(data.token, true)) .catch(error => console.error(error)) .then(() => { From 8d297a09183ed727414cbd8ed57ffb415aa282b9 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 16 Dec 2020 19:24:39 -0700 Subject: [PATCH 04/14] Release reserved allocations upon archive failure --- .../Api/Remote/Servers/ServerTransferController.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index 8ee2c242f..cc9d197a4 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -112,10 +112,18 @@ class ServerTransferController extends Controller // Unsuspend the server and don't continue the transfer. if (! $request->input('successful')) { - $server->transfer->forceFill([ + $transfer = $server->transfer; + + $transfer->forceFill([ 'successful' => false, ])->saveOrFail(); + $allocationIds = json_decode($transfer->new_additional_allocations); + array_push($allocationIds, $transfer->new_allocation); + + // Release the reserved allocations. + $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } From 5668a780e287151bf9f4292763d98e20a68c3d1c Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 16 Dec 2020 19:30:12 -0700 Subject: [PATCH 05/14] Hopefully the last small tweaks and fixes to transfer logs --- resources/scripts/components/server/Console.tsx | 6 +++++- .../scripts/components/server/TransferListener.tsx | 10 +++++++--- .../scripts/components/server/WebsocketHandler.tsx | 8 ++------ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index 74de72c04..26e214a36 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -67,6 +67,7 @@ export default () => { const { connected, instance } = ServerContext.useStoreState(state => state.socket); const [ canSendCommands ] = usePermissions([ 'control.console' ]); const serverId = ServerContext.useStoreState(state => state.server.data!.id); + const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring) const [ history, setHistory ] = usePersistedState(`${serverId}:command_history`, []); const [ historyIndex, setHistoryIndex ] = useState(-1); @@ -165,7 +166,10 @@ export default () => { useEffect(() => { if (connected && instance) { - // terminal.clear(); + // Do not clear the console if the server is being transferred. + if (!isTransferring) { + terminal.clear(); + } instance.addListener('status', handlePowerChangeEvent); instance.addListener('console output', handleConsoleOutput); diff --git a/resources/scripts/components/server/TransferListener.tsx b/resources/scripts/components/server/TransferListener.tsx index ce9c84793..b46e655bc 100644 --- a/resources/scripts/components/server/TransferListener.tsx +++ b/resources/scripts/components/server/TransferListener.tsx @@ -6,19 +6,23 @@ const TransferListener = () => { const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); - // Listen for the installation completion event and then fire off a request to fetch the updated - // server information. This allows the server to automatically become available to the user if they - // just sit on the page. + // Listen for the transfer status event so we can update the state of the server. useWebsocketEvent('transfer status', (status: string) => { if (status === 'starting') { setServerFromState(s => ({ ...s, isTransferring: true })); return; } + if (status === 'failure') { + setServerFromState(s => ({ ...s, isTransferring: false })); + return; + } + if (status !== 'success') { return; } + // Refresh the server's information as it's node and allocations were just updated. getServer(uuid).catch(error => console.error(error)); }); diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index d519da4f5..10b91dd47 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -53,16 +53,12 @@ export default () => { return; } - // Force a reconnection to the websocket which will connect us - // to the target node instead of the source node. - - // Close the current websocket connection. + // This code forces a reconnection to the websocket which will connect us to the target node instead of the source node + // in order to be able to receive transfer logs from the target node. socket.close(); - setError('connecting'); setConnectionState(false); setInstance(null); - connect(uuid); }); From fd848985ee952c0374b508477ff685ac1156cfbc Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 17 Dec 2020 10:34:26 -0700 Subject: [PATCH 06/14] Add ServerTransferringException, use is_null --- .../Http/Server/ServerTransferringException.php | 14 ++++++++++++++ .../Api/Client/Servers/WebsocketController.php | 2 +- .../Api/Remote/SftpAuthenticationController.php | 12 +++++++++--- .../Api/Client/Server/AuthenticateServerAccess.php | 2 +- .../Middleware/Server/AccessingValidServer.php | 5 +++-- app/Services/Servers/SuspensionService.php | 7 ++++--- app/Transformers/Api/Client/ServerTransformer.php | 2 +- resources/scripts/routers/ServerRouter.tsx | 2 +- .../views/admin/servers/view/manage.blade.php | 4 ++-- 9 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 app/Exceptions/Http/Server/ServerTransferringException.php diff --git a/app/Exceptions/Http/Server/ServerTransferringException.php b/app/Exceptions/Http/Server/ServerTransferringException.php new file mode 100644 index 000000000..91c3f39bd --- /dev/null +++ b/app/Exceptions/Http/Server/ServerTransferringException.php @@ -0,0 +1,14 @@ +permissionsService->handle($server, $user); $node = null; - if ($server->transfer !== null) { + if (! is_null($server->transfer)) { // Check if the user has permissions to receive transfer logs. if (! in_array('admin.websocket.transfer', $permissions)) { throw new HttpException(Response::HTTP_FORBIDDEN, 'You do not have permission to view transfer logs'); diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index 7fb5625f4..31862a567 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -12,6 +12,7 @@ use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\GetUserPermissionsService; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Pterodactyl\Exceptions\Http\Server\ServerTransferringException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; @@ -110,9 +111,14 @@ class SftpAuthenticationController extends Controller } } - // Remeber, for security purposes, only reveal the existence of the server to people that + // Prevent SFTP access to servers that are being transferred. + if (! is_null($server->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->installed !== 1 || $server->suspended || $server->transfer !== null) { + if ($server->installed !== 1 || $server->suspended) { throw new BadRequestHttpException( 'Server is not installed or is currently suspended.' ); @@ -132,7 +138,7 @@ class SftpAuthenticationController extends Controller * @param \Illuminate\Http\Request $request * @return string */ - protected function throttleKey(Request $request) + protected function throttleKey(Request $request): string { $username = explode('.', strrev($request->input('username', ''))); diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index eee798198..2a08a1cb4 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -79,7 +79,7 @@ class AuthenticateServerAccess } } - if ($server->transfer !== null) { + if (! is_null($server->transfer)) { if (! $user->root_admin || ($user->root_admin && ! $request->routeIs($this->except))) { throw new ConflictHttpException('Server is currently being transferred.'); } diff --git a/app/Http/Middleware/Server/AccessingValidServer.php b/app/Http/Middleware/Server/AccessingValidServer.php index 1bed5f556..9c73112dc 100644 --- a/app/Http/Middleware/Server/AccessingValidServer.php +++ b/app/Http/Middleware/Server/AccessingValidServer.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; +use Pterodactyl\Exceptions\Http\Server\ServerTransferringException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class AccessingValidServer @@ -80,9 +81,9 @@ class AccessingValidServer return $this->response->view('errors.installing', [], 409); } - if ($server->transfer !== null) { + if (! is_null($server->transfer)) { if ($isApiRequest) { - throw new ConflictHttpException('Server is currently being transferred.'); + throw new ServerTransferringException(); } return $this->response->view('errors.transferring', [], 409); diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index e1a0a805a..83de0ad08 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -7,6 +7,7 @@ use Pterodactyl\Models\Server; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; +use Pterodactyl\Exceptions\Http\Server\ServerTransferringException; class SuspensionService { @@ -58,8 +59,8 @@ class SuspensionService } // Check if the server is currently being transferred. - if ($server->transfer !== null) { - throw new ConflictHttpException('Server is currently being transferred'); + if (! is_null($server->transfer)) { + throw new ServerTransferringException(); } $this->connection->transaction(function () use ($action, $server) { @@ -68,7 +69,7 @@ class SuspensionService ]); // Only send the suspension request to wings if the server is not currently being transferred. - if ($server->transfer === null) { + if (is_null($server->transfer)) { $this->daemonServerRepository->setServer($server)->suspend($action === self::ACTION_UNSUSPEND); } }); diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index 766b3b726..0673f9b57 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -72,7 +72,7 @@ class ServerTransformer extends BaseClientTransformer ], 'is_suspended' => $server->suspended, 'is_installing' => $server->installed !== 1, - 'is_transferring' => $server->transfer !== null, + 'is_transferring' => ! is_null($server->transfer), ]; } diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index d01f76fcd..533491db6 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -66,7 +66,7 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) getServer(match.params.id) .catch(error => { if (error.response?.status === 409) { - if (error.response.data?.errors[0]?.detail?.includes('transfer')) { + if (error.response.data?.errors[0]?.code === 'ServerTransferringException') { setTransferring(true); } else { setInstalling(true); diff --git a/resources/views/admin/servers/view/manage.blade.php b/resources/views/admin/servers/view/manage.blade.php index af4e93e1b..9962cfddf 100644 --- a/resources/views/admin/servers/view/manage.blade.php +++ b/resources/views/admin/servers/view/manage.blade.php @@ -72,7 +72,7 @@
{!! csrf_field() !!} - +
@@ -97,7 +97,7 @@ @endif - @if($server->transfer === null) + @if(is_null($server->transfer))
From e69d9b2c267dbc5bac658856922e8aa2c237364a Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 17 Dec 2020 10:35:23 -0700 Subject: [PATCH 07/14] Update comment in AuthenticateServerAccess.php --- .../Middleware/Api/Client/Server/AuthenticateServerAccess.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 2a08a1cb4..5dae378d4 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -69,7 +69,7 @@ class AuthenticateServerAccess ); } - // Still allow users to get information about there server if it is installing or being transferred. + // 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 From 37cfa151b630a207ec16024732da0a18d62ccb47 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 17 Dec 2020 10:37:14 -0700 Subject: [PATCH 08/14] Use ServerTransferringException --- .../Middleware/Api/Client/Server/AuthenticateServerAccess.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 5dae378d4..16713b77b 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -9,6 +9,7 @@ use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Pterodactyl\Exceptions\Http\Server\ServerTransferringException; class AuthenticateServerAccess { @@ -81,7 +82,7 @@ class AuthenticateServerAccess if (! is_null($server->transfer)) { if (! $user->root_admin || ($user->root_admin && ! $request->routeIs($this->except))) { - throw new ConflictHttpException('Server is currently being transferred.'); + throw new ServerTransferringException(); } } } From d8f75fa0b7cde9ad94f4c25faed4dc499d6ba576 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Thu, 17 Dec 2020 11:14:58 -0700 Subject: [PATCH 09/14] Fix failed transfers locking a server into a unaccessible state --- .../Servers/ServerTransferController.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index cc9d197a4..32c56e98a 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -12,7 +12,6 @@ use Illuminate\Http\JsonResponse; use Lcobucci\JWT\Signer\Hmac\Sha256; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Servers\SuspensionService; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Wings\DaemonServerRepository; @@ -114,9 +113,8 @@ class ServerTransferController extends Controller if (! $request->input('successful')) { $transfer = $server->transfer; - $transfer->forceFill([ - 'successful' => false, - ])->saveOrFail(); + $transfer->successful = false; + $transfer->saveOrFail(); $allocationIds = json_decode($transfer->new_additional_allocations); array_push($allocationIds, $transfer->new_allocation); @@ -189,9 +187,19 @@ class ServerTransferController extends Controller $allocationIds = json_decode($transfer->new_additional_allocations); array_push($allocationIds, $transfer->new_allocation); + // Begin a transaction. + $this->connection->beginTransaction(); + + // Mark the transfer as unsuccessful. + $transfer->successful = false; + $transfer->saveOrFail(); + // Remove the new allocations. $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); + // Commit the transaction. + $this->connection->commit(); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -236,9 +244,6 @@ class ServerTransferController extends Controller $this->writer->warning($exception); } - // Unsuspend the server - $server->load('node'); - return new JsonResponse([], Response::HTTP_NO_CONTENT); } } From bb559422dcd24f64bfff103eb05ce8f89bacdfcb Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Sat, 19 Dec 2020 11:51:26 -0700 Subject: [PATCH 10/14] Make ESLint happy, fix minor issue with file manager breadcrumb --- resources/scripts/components/server/Console.tsx | 2 +- .../scripts/components/server/files/FileManagerContainer.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index 26e214a36..111fc1d11 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -67,7 +67,7 @@ export default () => { const { connected, instance } = ServerContext.useStoreState(state => state.socket); const [ canSendCommands ] = usePermissions([ 'control.console' ]); const serverId = ServerContext.useStoreState(state => state.server.data!.id); - const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring) + const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring); const [ history, setHistory ] = usePersistedState(`${serverId}:command_history`, []); const [ historyIndex, setHistoryIndex ] = useState(-1); diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index 613a1baaa..565716296 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -65,7 +65,7 @@ export default () => { } From 6c6157769997c762ef47ac16e28a85215b997d58 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 24 Dec 2020 09:20:23 -0800 Subject: [PATCH 11/14] Simplify logic in websocket control --- .../Api/Client/Servers/WebsocketController.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php index b3ddaae96..1de41b08e 100644 --- a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php +++ b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php @@ -3,12 +3,11 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Carbon\CarbonImmutable; -use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Permission; use Pterodactyl\Services\Nodes\NodeJWTService; -use Symfony\Component\HttpKernel\Exception\HttpException; +use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Services\Servers\GetUserPermissionsService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -55,26 +54,22 @@ class WebsocketController extends ClientApiController { $user = $request->user(); if ($user->cannot(Permission::ACTION_WEBSOCKET_CONNECT, $server)) { - throw new HttpException(Response::HTTP_FORBIDDEN, 'You do not have permission to connect to this server\'s websocket.'); + throw new HttpForbiddenException('You do not have permission to connect to this server\'s websocket.'); } $permissions = $this->permissionsService->handle($server, $user); - $node = null; + $node = $server->node; if (! is_null($server->transfer)) { // Check if the user has permissions to receive transfer logs. if (! in_array('admin.websocket.transfer', $permissions)) { - throw new HttpException(Response::HTTP_FORBIDDEN, 'You do not have permission to view transfer logs'); + throw new HttpForbiddenException('You do not have permission to view server transfer logs.'); } // Redirect the websocket request to the new node if the server has been archived. if ($server->transfer->archived) { $node = $server->transfer->newNode; - } else { - $node = $server->node; } - } else { - $node = $server->node; } $token = $this->jwtService From 2ee08a1a3dc0ead5a5355777b766d0b28fe01f69 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 24 Dec 2020 10:10:40 -0800 Subject: [PATCH 12/14] Update logic for server transfer controller --- .../Servers/ServerTransferController.php | 199 ++++++++---------- app/Models/ServerTransfer.php | 14 +- app/Services/Eggs/EggConfigurationService.php | 2 +- app/Services/Nodes/NodeJWTService.php | 29 ++- .../ServerConfigurationStructureService.php | 31 ++- ..._24_092449_make_allocation_fields_json.php | 34 +++ 6 files changed, 178 insertions(+), 131 deletions(-) create mode 100644 database/migrations/2020_12_24_092449_make_allocation_fields_json.php diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php index 32c56e98a..cc097a637 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php @@ -3,20 +3,19 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; use Cake\Chronos\Chronos; -use Lcobucci\JWT\Builder; +use Illuminate\Support\Arr; use Illuminate\Http\Request; -use Lcobucci\JWT\Signer\Key; -use Psr\Log\LoggerInterface; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; -use Lcobucci\JWT\Signer\Hmac\Sha256; +use Pterodactyl\Models\Allocation; +use Illuminate\Support\Facades\Log; +use Pterodactyl\Models\ServerTransfer; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\Eloquent\NodeRepository; +use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Wings\DaemonServerRepository; use Pterodactyl\Repositories\Wings\DaemonTransferRepository; -use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Services\Servers\ServerConfigurationStructureService; @@ -32,16 +31,6 @@ class ServerTransferController extends Controller */ private $repository; - /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface - */ - private $allocationRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\NodeRepository - */ - private $nodeRepository; - /** * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository */ @@ -58,40 +47,34 @@ class ServerTransferController extends Controller private $configurationStructureService; /** - * @var \Psr\Log\LoggerInterface + * @var \Pterodactyl\Services\Nodes\NodeJWTService */ - private $writer; + private $jwtService; /** * ServerTransferController constructor. * * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository - * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository - * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository * @param \Pterodactyl\Repositories\Wings\DaemonTransferRepository $daemonTransferRepository * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService - * @param \Psr\Log\LoggerInterface $writer + * @param \Pterodactyl\Services\Nodes\NodeJWTService $jwtService */ public function __construct( ConnectionInterface $connection, ServerRepository $repository, - AllocationRepositoryInterface $allocationRepository, - NodeRepository $nodeRepository, DaemonServerRepository $daemonServerRepository, DaemonTransferRepository $daemonTransferRepository, ServerConfigurationStructureService $configurationStructureService, - LoggerInterface $writer + NodeJWTService $jwtService ) { $this->connection = $connection; $this->repository = $repository; - $this->allocationRepository = $allocationRepository; - $this->nodeRepository = $nodeRepository; $this->daemonServerRepository = $daemonServerRepository; $this->daemonTransferRepository = $daemonTransferRepository; $this->configurationStructureService = $configurationStructureService; - $this->writer = $writer; + $this->jwtService = $jwtService; } /** @@ -101,7 +84,6 @@ class ServerTransferController extends Controller * @param string $uuid * @return \Illuminate\Http\JsonResponse * - * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Throwable */ @@ -111,62 +93,43 @@ class ServerTransferController extends Controller // Unsuspend the server and don't continue the transfer. if (! $request->input('successful')) { - $transfer = $server->transfer; - - $transfer->successful = false; - $transfer->saveOrFail(); - - $allocationIds = json_decode($transfer->new_additional_allocations); - array_push($allocationIds, $transfer->new_allocation); - - // Release the reserved allocations. - $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); - - return new JsonResponse([], Response::HTTP_NO_CONTENT); + return $this->processFailedTransfer($server->transfer); } - $server->node_id = $server->transfer->new_node; - - $data = $this->configurationStructureService->handle($server); - $data['suspended'] = false; - $data['service']['skip_scripts'] = true; + // We want to generate a new configuration using the new node_id value from the + // transfer, and not the old node value. + $data = $this->configurationStructureService->handle($server, [ + 'node_id' => $server->transfer->new_node, + ]); $allocations = $server->getAllocationMappings(); - $data['allocations']['default']['ip'] = array_key_first($allocations); - $data['allocations']['default']['port'] = $allocations[$data['allocations']['default']['ip']][0]; + $primary = array_key_first($allocations); + Arr::set($data, 'allocations.default.ip', $primary); + Arr::set($data, 'allocations.default.port', $allocations[$primary][0]); + Arr::set($data, 'service.skip_scripts', true); + Arr::set($data, 'suspended', false); - $now = Chronos::now(); - $signer = new Sha256; + $this->connection->transaction(function () use ($data, $server) { + // This token is used by the new node the server is being transfered to. It allows + // that node to communicate with the old node during the process to initiate the + // actual file transfer. + $token = $this->jwtService + ->setExpiresAt(Chronos::now()->addMinutes(15)) + ->setSubject($server->uuid) + ->handle($server->node, $server->uuid, 'sha256'); - $token = (new Builder)->issuedBy(config('app.url')) - ->permittedFor($server->node->getConnectionAddress()) - ->identifiedBy(hash('sha256', $server->uuid), true) - ->issuedAt($now->getTimestamp()) - ->canOnlyBeUsedAfter($now->getTimestamp()) - ->expiresAt($now->addMinutes(15)->getTimestamp()) - ->relatedTo($server->uuid, true) - ->getToken($signer, new Key($server->node->getDecryptedKey())); - - // Update the archived field on the transfer to make clients connect to the websocket - // on the new node to be able to receive transfer logs. - $server->transfer->forceFill([ - 'archived' => true, - ])->saveOrFail(); - - // On the daemon transfer repository, make sure to set the node after the server - // because setServer() tells the repository to use the server's node and not the one - // we want to specify. - try { - /** @var \Pterodactyl\Models\Node $newNode */ - $newNode = $this->nodeRepository->find($server->transfer->new_node); + // Update the archived field on the transfer to make clients connect to the websocket + // on the new node to be able to receive transfer logs. + $server->transfer->forceFill(['archived' => true])->saveOrFail(); + // On the daemon transfer repository, make sure to set the node after the server + // because setServer() tells the repository to use the server's node and not the one + // we want to specify. $this->daemonTransferRepository ->setServer($server) - ->setNode($newNode) + ->setNode($server->transfer->newNode) ->notify($server, $data, $server->node, $token->__toString()); - } catch (DaemonConnectionException $exception) { - throw $exception; - } + }); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -182,25 +145,8 @@ class ServerTransferController extends Controller public function failure(string $uuid) { $server = $this->repository->getByUuid($uuid); - $transfer = $server->transfer; - $allocationIds = json_decode($transfer->new_additional_allocations); - array_push($allocationIds, $transfer->new_allocation); - - // Begin a transaction. - $this->connection->beginTransaction(); - - // Mark the transfer as unsuccessful. - $transfer->successful = false; - $transfer->saveOrFail(); - - // Remove the new allocations. - $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); - - // Commit the transaction. - $this->connection->commit(); - - return new JsonResponse([], Response::HTTP_NO_CONTENT); + return $this->processFailedTransfer($server->transfer); } /** @@ -216,34 +162,63 @@ class ServerTransferController extends Controller $server = $this->repository->getByUuid($uuid); $transfer = $server->transfer; - $allocationIds = json_decode($transfer->old_additional_allocations); - array_push($allocationIds, $transfer->old_allocation); + /** @var \Pterodactyl\Models\Server $server */ + $server = $this->connection->transaction(function () use ($server, $transfer) { + $allocations = [$transfer->old_allocation]; + if (! empty($transfer->old_additional_allocations)) { + array_push($allocations, $transfer->old_additional_allocations); + } - // Begin a transaction. - $this->connection->beginTransaction(); + // Remove the old allocations for the server and re-assign the server to the new + // primary allocation and node. + Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]); + $server->update([ + 'allocation_id' => $transfer->new_allocation, + 'node_id' => $transfer->new_node, + ]); - // Remove the old allocations. - $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]); + $server = $server->fresh(); + $server->transfer->update(['successful' => true]); - // Update the server's allocation_id and node_id. - $server->allocation_id = $transfer->new_allocation; - $server->node_id = $transfer->new_node; - $server->saveOrFail(); + return $server; + }); - // Mark the transfer as successful. - $transfer->successful = true; - $transfer->saveOrFail(); - - // Commit the transaction. - $this->connection->commit(); - - // Delete the server from the old node + // Delete the server from the old node making sure to point it to the old node so + // that we do not delete it from the new node the server was transfered to. try { - $this->daemonServerRepository->setServer($server)->delete(); + $this->daemonServerRepository + ->setServer($server) + ->setNode($transfer->oldNode) + ->delete(); } catch (DaemonConnectionException $exception) { - $this->writer->warning($exception); + Log::warning($exception, ['transfer_id' => $server->transfer->id]); } return new JsonResponse([], Response::HTTP_NO_CONTENT); } + + /** + * Release all of the reserved allocations for this transfer and mark it as failed in + * the database. + * + * @param \Pterodactyl\Models\ServerTransfer $transfer + * @return \Illuminate\Http\JsonResponse + * + * @throws \Throwable + */ + protected function processFailedTransfer(ServerTransfer $transfer) + { + $this->connection->transaction(function () use (&$transfer) { + $transfer->forceFill(['successful' => false])->saveOrFail(); + + $allocations = [$transfer->new_allocation]; + if (! empty($transfer->new_additional_allocations)) { + array_push($allocations, $transfer->new_additional_allocations); + } + + Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]); + }); + + return new JsonResponse([], Response::HTTP_NO_CONTENT); + } } diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php index cb95fefc7..1af9ab7e4 100644 --- a/app/Models/ServerTransfer.php +++ b/app/Models/ServerTransfer.php @@ -9,8 +9,8 @@ namespace Pterodactyl\Models; * @property int $new_node * @property int $old_allocation * @property int $new_allocation - * @property string $old_additional_allocations - * @property string $new_additional_allocations + * @property array|null $old_additional_allocations + * @property array|null $new_additional_allocations * @property bool|null $successful * @property bool $archived * @property \Carbon\Carbon $created_at @@ -53,8 +53,8 @@ class ServerTransfer extends Model 'new_node' => 'int', 'old_allocation' => 'int', 'new_allocation' => 'int', - 'old_additional_allocations' => 'string', - 'new_additional_allocations' => 'string', + 'old_additional_allocations' => 'array', + 'new_additional_allocations' => 'array', 'successful' => 'bool', 'archived' => 'bool', ]; @@ -68,8 +68,10 @@ class ServerTransfer extends Model 'new_node' => 'required|numeric', 'old_allocation' => 'required|numeric', 'new_allocation' => 'required|numeric', - 'old_additional_allocations' => 'nullable', - 'new_additional_allocations' => 'nullable', + 'old_additional_allocations' => 'nullable|array', + 'old_additional_allocations.*' => 'numeric', + 'new_additional_allocations' => 'nullable|array', + 'new_additional_allocations.*' => 'numeric', 'successful' => 'sometimes|nullable|boolean', ]; diff --git a/app/Services/Eggs/EggConfigurationService.php b/app/Services/Eggs/EggConfigurationService.php index 6dbb469ba..4d1db4e2e 100644 --- a/app/Services/Eggs/EggConfigurationService.php +++ b/app/Services/Eggs/EggConfigurationService.php @@ -102,7 +102,7 @@ class EggConfigurationService { // Get the legacy configuration structure for the server so that we // can property map the egg placeholders to values. - $structure = $this->configurationStructureService->handle($server, true); + $structure = $this->configurationStructureService->handle($server, [], true); $response = []; // Normalize the output of the configuration for the new Wings Daemon to more diff --git a/app/Services/Nodes/NodeJWTService.php b/app/Services/Nodes/NodeJWTService.php index 85332a6bc..7c359efe8 100644 --- a/app/Services/Nodes/NodeJWTService.php +++ b/app/Services/Nodes/NodeJWTService.php @@ -22,6 +22,11 @@ class NodeJWTService */ private $expiresAt; + /** + * @var string|null + */ + private $subject; + /** * Set the claims to include in this JWT. * @@ -35,6 +40,10 @@ class NodeJWTService return $this; } + /** + * @param \DateTimeInterface $date + * @return $this + */ public function setExpiresAt(DateTimeInterface $date) { $this->expiresAt = $date->getTimestamp(); @@ -42,20 +51,32 @@ class NodeJWTService return $this; } + /** + * @param string $subject + * @return $this + */ + public function setSubject(string $subject) + { + $this->subject = $subject; + + return $this; + } + /** * Generate a new JWT for a given node. * * @param \Pterodactyl\Models\Node $node * @param string|null $identifiedBy + * @param string $algo * @return \Lcobucci\JWT\Token */ - public function handle(Node $node, string $identifiedBy) + public function handle(Node $node, string $identifiedBy, string $algo = 'md5') { $signer = new Sha256; $builder = (new Builder)->issuedBy(config('app.url')) ->permittedFor($node->getConnectionAddress()) - ->identifiedBy(md5($identifiedBy), true) + ->identifiedBy(hash($algo, $identifiedBy), true) ->issuedAt(CarbonImmutable::now()->getTimestamp()) ->canOnlyBeUsedAfter(CarbonImmutable::now()->subMinutes(5)->getTimestamp()); @@ -63,6 +84,10 @@ class NodeJWTService $builder = $builder->expiresAt($this->expiresAt); } + if (!empty($this->subject)) { + $builder = $builder->relatedTo($this->subject, true); + } + foreach ($this->claims as $key => $value) { $builder = $builder->withClaim($key, $value); } diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index dc2a4bfb2..790e9ecc1 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -29,14 +29,25 @@ class ServerConfigurationStructureService * daemon, if you modify the structure eggs will break unexpectedly. * * @param \Pterodactyl\Models\Server $server + * @param array $override * @param bool $legacy deprecated * @return array */ - public function handle(Server $server, bool $legacy = false): array + public function handle(Server $server, array $override = [], bool $legacy = false): array { - return $legacy ? - $this->returnLegacyFormat($server) - : $this->returnCurrentFormat($server); + $clone = $server; + // If any overrides have been set on this call make sure to update them on the + // cloned instance so that the configuration generated uses them. + if (!empty($override)) { + $clone = $server->fresh(); + foreach ($override as $key => $value) { + $clone->setAttribute($key, $value); + } + } + + return $legacy + ? $this->returnLegacyFormat($clone) + : $this->returnCurrentFormat($clone); } /** @@ -105,12 +116,12 @@ class ServerConfigurationStructureService })->toArray(), 'env' => $this->environment->handle($server), 'oom_disabled' => $server->oom_disabled, - 'memory' => (int) $server->memory, - 'swap' => (int) $server->swap, - 'io' => (int) $server->io, - 'cpu' => (int) $server->cpu, + 'memory' => (int)$server->memory, + 'swap' => (int)$server->swap, + 'io' => (int)$server->io, + 'cpu' => (int)$server->cpu, 'threads' => $server->threads, - 'disk' => (int) $server->disk, + 'disk' => (int)$server->disk, 'image' => $server->image, ], 'service' => [ @@ -118,7 +129,7 @@ class ServerConfigurationStructureService 'skip_scripts' => $server->skip_scripts, ], 'rebuild' => false, - 'suspended' => (int) $server->suspended, + 'suspended' => (int)$server->suspended, ]; } } diff --git a/database/migrations/2020_12_24_092449_make_allocation_fields_json.php b/database/migrations/2020_12_24_092449_make_allocation_fields_json.php new file mode 100644 index 000000000..0f1ff554f --- /dev/null +++ b/database/migrations/2020_12_24_092449_make_allocation_fields_json.php @@ -0,0 +1,34 @@ +json('old_additional_allocations')->nullable()->change(); + $table->json('new_additional_allocations')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('server_transfers', function (Blueprint $table) { + $table->string('old_additional_allocations')->nullable()->change(); + $table->string('new_additional_allocations')->nullable()->change(); + }); + } +} From a2548c14ac06909cc7a4ad55e6bc33e8b205e4d6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 24 Dec 2020 10:12:01 -0800 Subject: [PATCH 13/14] Fix logic since this accepts arrays now --- .../Controllers/Admin/Servers/ServerTransferController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php index dff8444a9..5207ef9d1 100644 --- a/app/Http/Controllers/Admin/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -114,8 +114,8 @@ class ServerTransferController extends Controller $transfer->new_node = $node_id; $transfer->old_allocation = $server->allocation_id; $transfer->new_allocation = $allocation_id; - $transfer->old_additional_allocations = json_encode($server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')); - $transfer->new_additional_allocations = json_encode($additional_allocations); + $transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id'); + $transfer->new_additional_allocations = $additional_allocations; $transfer->save(); From 6c39288defd8d1a83b10fdeb953cd8ef6067e9af Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 24 Dec 2020 10:14:10 -0800 Subject: [PATCH 14/14] Clarify error messaging for transfers --- app/Exceptions/Http/Server/ServerTransferringException.php | 5 ++++- .../Controllers/Api/Remote/SftpAuthenticationController.php | 2 +- .../Api/Client/Server/AuthenticateServerAccess.php | 2 +- app/Http/Middleware/Server/AccessingValidServer.php | 2 +- app/Services/Servers/SuspensionService.php | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/Exceptions/Http/Server/ServerTransferringException.php b/app/Exceptions/Http/Server/ServerTransferringException.php index 91c3f39bd..d36fb8de4 100644 --- a/app/Exceptions/Http/Server/ServerTransferringException.php +++ b/app/Exceptions/Http/Server/ServerTransferringException.php @@ -7,8 +7,11 @@ use Symfony\Component\HttpKernel\Exception\HttpException; class ServerTransferringException extends HttpException { + /** + * ServerTransferringException constructor. + */ public function __construct() { - parent::__construct(Response::HTTP_CONFLICT, 'Server is currently being transferred.'); + parent::__construct(Response::HTTP_CONFLICT, 'This server is currently being transferred to a new machine, please try again laster.'); } } diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index 31862a567..cab532e81 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -113,7 +113,7 @@ class SftpAuthenticationController extends Controller // Prevent SFTP access to servers that are being transferred. if (! is_null($server->transfer)) { - throw new ServerTransferringException(); + throw new ServerTransferringException; } // Remember, for security purposes, only reveal the existence of the server to people that diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 16713b77b..e9eaa143e 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -82,7 +82,7 @@ class AuthenticateServerAccess if (! is_null($server->transfer)) { if (! $user->root_admin || ($user->root_admin && ! $request->routeIs($this->except))) { - throw new ServerTransferringException(); + throw new ServerTransferringException; } } } diff --git a/app/Http/Middleware/Server/AccessingValidServer.php b/app/Http/Middleware/Server/AccessingValidServer.php index 9c73112dc..2491414c7 100644 --- a/app/Http/Middleware/Server/AccessingValidServer.php +++ b/app/Http/Middleware/Server/AccessingValidServer.php @@ -83,7 +83,7 @@ class AccessingValidServer if (! is_null($server->transfer)) { if ($isApiRequest) { - throw new ServerTransferringException(); + throw new ServerTransferringException; } return $this->response->view('errors.transferring', [], 409); diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index 83de0ad08..87fd0a334 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -60,7 +60,7 @@ class SuspensionService // Check if the server is currently being transferred. if (! is_null($server->transfer)) { - throw new ServerTransferringException(); + throw new ServerTransferringException; } $this->connection->transaction(function () use ($action, $server) {