From 54554465f2e45abed47ae5b405774ba8fe91dabb Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 3 Sep 2017 16:32:52 -0500 Subject: [PATCH] Add more front-end controllers, language file cleanup --- .../Daemon/FileRepositoryInterface.php | 8 + .../Http/Server/FileSizeTooLargeException.php | 31 +++ .../Server/FileTypeNotEditableException.php | 31 +++ .../Controllers/Server/AjaxController.php | 129 ----------- .../Controllers/Server/ConsoleController.php | 10 +- .../Server/Files/DownloadController.php | 76 ++++++ .../Server/Files/FileActionsController.php | 161 +++++++++++++ .../Server/Files/RemoteRequestController.php | 166 ++++++++++++++ .../Controllers/Server/ServerController.php | 121 ---------- .../Server/UpdateFileContentsFormRequest.php | 127 ++++++++++ .../Server/ServerDataComposer.php | 60 +++++ app/Providers/ViewComposerServiceProvider.php | 39 ++++ app/Repositories/Daemon/FileRepository.php | 2 +- .../Allocations/AssignmentService.php | 6 +- app/Services/Database/DatabaseHostService.php | 2 +- app/Services/Nodes/NodeDeletionService.php | 2 +- app/Services/Nodes/NodeUpdateService.php | 2 +- app/Services/Packs/PackCreationService.php | 4 +- app/Services/Packs/PackDeletionService.php | 2 +- app/Services/Packs/PackUpdateService.php | 2 +- app/Services/Packs/TemplateUploadService.php | 10 +- .../Options/InstallScriptUpdateService.php | 2 +- .../Options/OptionCreationService.php | 2 +- .../Options/OptionDeletionService.php | 2 +- .../Services/Options/OptionUpdateService.php | 2 +- .../Services/ServiceDeletionService.php | 2 +- .../Variables/VariableUpdateService.php | 4 +- .../Subusers/SubuserCreationService.php | 6 +- .../Subusers/SubuserDeletionService.php | 2 +- .../Subusers/SubuserUpdateService.php | 2 +- ...Javascript.php => JavascriptInjection.php} | 2 +- config/app.php | 1 + resources/lang/en/{admin => }/exceptions.php | 0 resources/lang/en/server.php | 5 + routes/server.php | 16 +- .../Assertions/ControllerAssertionsTrait.php | 13 +- .../Controllers/Base/APIControllerTest.php | 2 +- .../Base/AccountControllerTest.php | 6 +- .../Base/SecurityControllerTest.php | 6 +- .../Server/ConsoleControllerTest.php | 11 +- .../Server/Files/DownloadControllerTest.php | 92 ++++++++ .../Files/FileActionsControllerTest.php | 217 ++++++++++++++++++ .../Allocations/AssignmentServiceTest.php | 6 +- .../Database/DatabaseHostServiceTest.php | 2 +- .../Nodes/NodeDeletionServiceTest.php | 2 +- .../Services/Nodes/NodeUpdateServiceTest.php | 2 +- .../Packs/PackCreationServiceTest.php | 4 +- .../Packs/PackDeletionServiceTest.php | 2 +- .../Services/Packs/PackUpdateServiceTest.php | 2 +- .../Packs/TemplateUploadServiceTest.php | 10 +- .../InstallScriptUpdateServiceTest.php | 2 +- .../Options/OptionCreationServiceTest.php | 2 +- .../Options/OptionDeletionServiceTest.php | 2 +- .../Options/OptionUpdateServiceTest.php | 2 +- .../Services/ServiceDeletionServiceTest.php | 2 +- .../Variables/VariableUpdateServiceTest.php | 2 +- .../Subusers/SubuserCreationServiceTest.php | 4 +- .../Subusers/SubuserDeletionServiceTest.php | 2 +- .../Subusers/SubuserUpdateServiceTest.php | 2 +- 59 files changed, 1100 insertions(+), 336 deletions(-) create mode 100644 app/Exceptions/Http/Server/FileSizeTooLargeException.php create mode 100644 app/Exceptions/Http/Server/FileTypeNotEditableException.php create mode 100644 app/Http/Controllers/Server/Files/DownloadController.php create mode 100644 app/Http/Controllers/Server/Files/FileActionsController.php create mode 100644 app/Http/Controllers/Server/Files/RemoteRequestController.php create mode 100644 app/Http/Requests/Server/UpdateFileContentsFormRequest.php create mode 100644 app/Http/ViewComposers/Server/ServerDataComposer.php create mode 100644 app/Providers/ViewComposerServiceProvider.php rename app/Traits/Controllers/{ServerToJavascript.php => JavascriptInjection.php} (98%) rename resources/lang/en/{admin => }/exceptions.php (100%) create mode 100644 tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php create mode 100644 tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php diff --git a/app/Contracts/Repository/Daemon/FileRepositoryInterface.php b/app/Contracts/Repository/Daemon/FileRepositoryInterface.php index a013bc1ae..d395794f3 100644 --- a/app/Contracts/Repository/Daemon/FileRepositoryInterface.php +++ b/app/Contracts/Repository/Daemon/FileRepositoryInterface.php @@ -31,6 +31,8 @@ interface FileRepositoryInterface extends BaseRepositoryInterface * * @param string $path * @return object + * + * @throws \GuzzleHttp\Exception\RequestException */ public function getFileStat($path); @@ -39,6 +41,8 @@ interface FileRepositoryInterface extends BaseRepositoryInterface * * @param string $path * @return object + * + * @throws \GuzzleHttp\Exception\RequestException */ public function getContent($path); @@ -48,6 +52,8 @@ interface FileRepositoryInterface extends BaseRepositoryInterface * @param string $path * @param string $content * @return \Psr\Http\Message\ResponseInterface + * + * @throws \GuzzleHttp\Exception\RequestException */ public function putContent($path, $content); @@ -56,6 +62,8 @@ interface FileRepositoryInterface extends BaseRepositoryInterface * * @param string $path * @return array + * + * @throws \GuzzleHttp\Exception\RequestException */ public function getDirectory($path); } diff --git a/app/Exceptions/Http/Server/FileSizeTooLargeException.php b/app/Exceptions/Http/Server/FileSizeTooLargeException.php new file mode 100644 index 000000000..b2da45b69 --- /dev/null +++ b/app/Exceptions/Http/Server/FileSizeTooLargeException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Http\Server; + +use Pterodactyl\Exceptions\DisplayException; + +class FileSizeTooLargeException extends DisplayException +{ +} diff --git a/app/Exceptions/Http/Server/FileTypeNotEditableException.php b/app/Exceptions/Http/Server/FileTypeNotEditableException.php new file mode 100644 index 000000000..96f9f4d4f --- /dev/null +++ b/app/Exceptions/Http/Server/FileTypeNotEditableException.php @@ -0,0 +1,31 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Exceptions\Http\Server; + +use Pterodactyl\Exceptions\DisplayException; + +class FileTypeNotEditableException extends DisplayException +{ +} diff --git a/app/Http/Controllers/Server/AjaxController.php b/app/Http/Controllers/Server/AjaxController.php index 01adb250e..f4b54ca86 100644 --- a/app/Http/Controllers/Server/AjaxController.php +++ b/app/Http/Controllers/Server/AjaxController.php @@ -30,7 +30,6 @@ use Illuminate\Http\Request; use Pterodactyl\Repositories; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Exceptions\DisplayValidationException; class AjaxController extends Controller { @@ -49,134 +48,6 @@ class AjaxController extends Controller */ protected $directory; - /** - * Returns a listing of files in a given directory for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View|\Illuminate\Http\Response - */ - public function postDirectoryList(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('list-files', $server); - - $this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/'); - $prevDir = [ - 'header' => ($this->directory !== '/') ? $this->directory : '', - ]; - if ($this->directory !== '/') { - $prevDir['first'] = true; - } - - // Determine if we should show back links in the file browser. - // This code is strange, and could probably be rewritten much better. - $goBack = explode('/', trim($this->directory, '/')); - if (! empty(array_filter($goBack)) && count($goBack) >= 2) { - $prevDir['show'] = true; - array_pop($goBack); - $prevDir['link'] = '/' . implode('/', $goBack); - $prevDir['link_show'] = implode('/', $goBack) . '/'; - } - - $controller = new Repositories\old_Daemon\FileRepository($uuid); - - try { - $directoryContents = $controller->returnDirectoryListing($this->directory); - } catch (DisplayException $ex) { - return response($ex->getMessage(), 500); - } catch (\Exception $ex) { - Log::error($ex); - - return response('An error occured while attempting to load the requested directory, please try again.', 500); - } - - return view('server.files.list', [ - 'server' => $server, - 'files' => $directoryContents->files, - 'folders' => $directoryContents->folders, - 'editableMime' => Repositories\HelperRepository::editableFiles(), - 'directory' => $prevDir, - ]); - } - - /** - * Handles a POST request to save a file. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\Response - */ - public function postSaveFile(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('save-files', $server); - - $controller = new Repositories\old_Daemon\FileRepository($uuid); - - try { - $controller->saveFileContents($request->input('file'), $request->input('contents')); - - return response(null, 204); - } catch (DisplayException $ex) { - return response($ex->getMessage(), 500); - } catch (\Exception $ex) { - Log::error($ex); - - return response('An error occured while attempting to save this file, please try again.', 500); - } - } - - /** - * Sets the primary allocation for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\JsonResponse - * @deprecated - */ - public function postSetPrimary(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid)->load('allocations'); - $this->authorize('set-connection', $server); - - if ((int) $request->input('allocation') === $server->allocation_id) { - return response()->json([ - 'error' => 'You are already using this as your default connection.', - ], 409); - } - - try { - $allocation = $server->allocations->where('id', $request->input('allocation'))->where('server_id', $server->id)->first(); - if (! $allocation) { - return response()->json([ - 'error' => 'No allocation matching your request was found in the system.', - ], 422); - } - - $repo = new Repositories\ServerRepository; - $repo->changeBuild($server->id, [ - 'default' => $allocation->ip . ':' . $allocation->port, - ]); - - return response('The default connection for this server has been updated. Please be aware that you will need to restart your server for this change to go into effect.'); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage(), true), - ], 422); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 503); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify the default connection for this server.', - ], 503); - } - } - /** * Resets a database password for a server. * diff --git a/app/Http/Controllers/Server/ConsoleController.php b/app/Http/Controllers/Server/ConsoleController.php index 8f43b1a2c..ca20852e9 100644 --- a/app/Http/Controllers/Server/ConsoleController.php +++ b/app/Http/Controllers/Server/ConsoleController.php @@ -26,12 +26,12 @@ namespace Pterodactyl\Http\Controllers\Server; use Illuminate\Contracts\Session\Session; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Traits\Controllers\ServerToJavascript; +use Pterodactyl\Traits\Controllers\JavascriptInjection; use Illuminate\Contracts\Config\Repository as ConfigRepository; class ConsoleController extends Controller { - use ServerToJavascript; + use JavascriptInjection; /** * @var \Illuminate\Contracts\Config\Repository @@ -77,7 +77,7 @@ class ConsoleController extends Controller ], ]); - return view('server.index', ['server' => $server, 'node' => $server->node]); + return view('server.index'); } /** @@ -87,13 +87,11 @@ class ConsoleController extends Controller */ public function console() { - $server = $this->session->get('server_data.model'); - $this->injectJavascript(['config' => [ 'console_count' => $this->config->get('pterodactyl.console.count'), 'console_freq' => $this->config->get('pterodactyl.console.frequency'), ]]); - return view('server.console', ['server' => $server, 'node' => $server->node]); + return view('server.console'); } } diff --git a/app/Http/Controllers/Server/Files/DownloadController.php b/app/Http/Controllers/Server/Files/DownloadController.php new file mode 100644 index 000000000..32d4cb5f9 --- /dev/null +++ b/app/Http/Controllers/Server/Files/DownloadController.php @@ -0,0 +1,76 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Server\Files; + +use Illuminate\Cache\Repository; +use Illuminate\Contracts\Session\Session; +use Pterodactyl\Http\Controllers\Controller; + +class DownloadController extends Controller +{ + /** + * @var \Illuminate\Cache\Repository + */ + protected $cache; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * DownloadController constructor. + * + * @param \Illuminate\Cache\Repository $cache + * @param \Illuminate\Contracts\Session\Session $session + */ + public function __construct(Repository $cache, Session $session) + { + $this->cache = $cache; + $this->session = $session; + } + + /** + * Setup a unique download link for a user to download a file from. + * + * @param string $uuid + * @param string $file + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index($uuid, $file) + { + $server = $this->session->get('server_data.model'); + $this->authorize('download-files', $server); + + $token = str_random(40); + $this->cache->tags(['Server:Downloads'])->put($token, ['server' => $server->uuid, 'path' => $file], 5); + + return redirect(sprintf( + '%s://%s:%s/server/file/download/%s', $server->node->scheme, $server->node->fqdn, $server->node->daemonListen, $token + )); + } +} diff --git a/app/Http/Controllers/Server/Files/FileActionsController.php b/app/Http/Controllers/Server/Files/FileActionsController.php new file mode 100644 index 000000000..902e713c2 --- /dev/null +++ b/app/Http/Controllers/Server/Files/FileActionsController.php @@ -0,0 +1,161 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Server\Files; + +use GuzzleHttp\Exception\RequestException; +use Illuminate\Contracts\Session\Session; +use Illuminate\Http\Request; +use Illuminate\Log\Writer; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest; +use Pterodactyl\Traits\Controllers\JavascriptInjection; + +class FileActionsController extends Controller +{ + use JavascriptInjection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface + */ + protected $fileRepository; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * FileActionsController constructor. + * + * @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $fileRepository + * @param \Illuminate\Contracts\Session\Session $session + * @param \Illuminate\Log\Writer $writer + */ + public function __construct(FileRepositoryInterface $fileRepository, Session $session, Writer $writer) + { + $this->fileRepository = $fileRepository; + $this->session = $session; + $this->writer = $writer; + } + + /** + * Display server file index list. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request) + { + $server = $this->session->get('server_data.model'); + $this->authorize('list-files', $server); + + $this->injectJavascript([ + 'meta' => [ + 'directoryList' => route('server.files.directory-list', $server->uuidShort), + 'csrftoken' => csrf_token(), + ], + 'permissions' => [ + 'moveFiles' => $request->user()->can('move-files', $server), + 'copyFiles' => $request->user()->can('copy-files', $server), + 'compressFiles' => $request->user()->can('compress-files', $server), + 'decompressFiles' => $request->user()->can('decompress-files', $server), + 'createFiles' => $request->user()->can('create-files', $server), + 'downloadFiles' => $request->user()->can('download-files', $server), + 'deleteFiles' => $request->user()->can('delete-files', $server), + ], + ]); + + return view('server.files.index'); + } + + /** + * Render page to manually create a file in the panel. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function create(Request $request) + { + $this->authorize('create-files', $this->session->get('server_data.model')); + $this->injectJavascript(); + + return view('server.files.add', [ + 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', + ]); + } + + /** + * Display a form to allow for editing of a file. + * + * @param \Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest $request + * @param string $uuid + * @param string $file + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateFileContentsFormRequest $request, $uuid, $file) + { + $server = $this->session->get('server_data.model'); + $this->authorize('edit-files', $server); + + $dirname = pathinfo($file, PATHINFO_DIRNAME); + try { + $content = $this->fileRepository->setNode($server->node_id) + ->setAccessServer($server->uuid) + ->setAccessToken($this->session->get('server_data.token')) + ->getContent($file); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + + $this->injectJavascript(['stat' => $request->getStats()]); + + return view('server.files.edit', [ + 'file' => $file, + 'stat' => $request->getStats(), + 'contents' => $content, + 'directory' => (in_array($dirname, ['.', './', '/'])) ? '/' : trim($dirname, '/') . '/', + ]); + } +} diff --git a/app/Http/Controllers/Server/Files/RemoteRequestController.php b/app/Http/Controllers/Server/Files/RemoteRequestController.php new file mode 100644 index 000000000..f5506fb1c --- /dev/null +++ b/app/Http/Controllers/Server/Files/RemoteRequestController.php @@ -0,0 +1,166 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Controllers\Server\Files; + +use GuzzleHttp\Exception\RequestException; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Illuminate\Contracts\Session\Session; +use Illuminate\Http\Request; +use Illuminate\Log\Writer; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Http\Controllers\Controller; + +class RemoteRequestController extends Controller +{ + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface + */ + protected $fileRepository; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * RemoteRequestController constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $fileRepository + * @param \Illuminate\Contracts\Session\Session $session + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConfigRepository $config, + FileRepositoryInterface $fileRepository, + Session $session, + Writer $writer + ) { + $this->config = $config; + $this->fileRepository = $fileRepository; + $this->session = $session; + $this->writer = $writer; + } + + /** + * Return a listing of a servers file directory. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse|\Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function directory(Request $request) + { + $server = $this->session->get('server_data.model'); + $this->authorize('list-files', $server); + + $requestDirectory = '/' . trim(urldecode($request->input('directory', '/')), '/'); + $directory = [ + 'header' => $requestDirectory !== '/' ? $requestDirectory : '', + 'first' => $requestDirectory !== '/', + ]; + + $goBack = explode('/', trim($requestDirectory, '/')); + if (! empty(array_filter($goBack)) && count($goBack) >= 2) { + array_pop($goBack); + + $directory['show'] = true; + $directory['link'] = '/' . implode('/', $goBack); + $directory['link_show'] = implode('/', $goBack) . '/'; + } + + try { + $listing = $this->fileRepository->setNode($server->node_id) + ->setAccessServer($server->uuid) + ->setAccessToken($this->session->get('server_data.token')) + ->getDirectory($requestDirectory); + } catch (RequestException $exception) { + $this->writer->warning($exception); + + if (! is_null($exception->getResponse())) { + return response()->json( + ['error' => $exception->getResponse()->getBody()], $exception->getResponse()->getStatusCode() + ); + } else { + return response()->json(['error' => trans('server.files.exceptions.list_directory')], 500); + } + } + + return view('server.files.list', [ + 'files' => $listing['files'], + 'folders' => $listing['folders'], + 'editableMime' => $this->config->get('pterodactyl.files.editable'), + 'directory' => $directory, + ]); + } + + /** + * Put the contents of a file onto the daemon. + * + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function store(Request $request, $uuid) + { + $server = $this->session->get('server_data.model'); + $this->authorize('save-files', $server); + + try { + $this->fileRepository->setNode($server->node_id) + ->setAccessServer($server->uuid) + ->setAccessToken($this->session->get('server_data.token')) + ->putContent($request->input('file'), $request->input('contents')); + + return response('', 204); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + if (! is_null($response)) { + return response()->json(['error' => $response->getBody()], $response->getStatusCode()); + } else { + return response()->json(['error' => trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])], 500); + } + } + } +} diff --git a/app/Http/Controllers/Server/ServerController.php b/app/Http/Controllers/Server/ServerController.php index c677c1cd6..e5bad57af 100644 --- a/app/Http/Controllers/Server/ServerController.php +++ b/app/Http/Controllers/Server/ServerController.php @@ -26,7 +26,6 @@ namespace Pterodactyl\Http\Controllers\Server; use Log; use Alert; -use Cache; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Exceptions\DisplayException; @@ -35,126 +34,6 @@ use Pterodactyl\Exceptions\DisplayValidationException; class ServerController extends Controller { - /** - * Renders file overview page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getFiles(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('list-files', $server); - - $server->js([ - 'meta' => [ - 'directoryList' => route('server.files.directory-list', $server->uuidShort), - 'csrftoken' => csrf_token(), - ], - 'permissions' => [ - 'moveFiles' => $request->user()->can('move-files', $server), - 'copyFiles' => $request->user()->can('copy-files', $server), - 'compressFiles' => $request->user()->can('compress-files', $server), - 'decompressFiles' => $request->user()->can('decompress-files', $server), - 'createFiles' => $request->user()->can('create-files', $server), - 'downloadFiles' => $request->user()->can('download-files', $server), - 'deleteFiles' => $request->user()->can('delete-files', $server), - ], - ]); - - return view('server.files.index', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Renders add file page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getAddFile(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('create-files', $server); - - $server->js(); - - return view('server.files.add', [ - 'server' => $server, - 'node' => $server->node, - 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', - ]); - } - - /** - * Renders edit file page for a given file. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param string $file - * @return \Illuminate\View\View - */ - public function getEditFile(Request $request, $uuid, $file) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('edit-files', $server); - - $fileInfo = (object) pathinfo($file); - $controller = new FileRepository($uuid); - - try { - $fileContent = $controller->returnFileContents($file); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - - return redirect()->route('server.files.index', $uuid); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to load the requested file for editing, please try again.')->flash(); - - return redirect()->route('server.files.index', $uuid); - } - - $server->js([ - 'stat' => $fileContent['stat'], - ]); - - return view('server.files.edit', [ - 'server' => $server, - 'node' => $server->node, - 'file' => $file, - 'stat' => $fileContent['stat'], - 'contents' => $fileContent['file']->content, - 'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/', - ]); - } - - /** - * Handles downloading a file for the user. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param string $file - * @return \Illuminate\View\View - */ - public function getDownloadFile(Request $request, $uuid, $file) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('download-files', $server); - - $token = str_random(40); - Cache::tags(['Server:Downloads'])->put($token, [ - 'server' => $server->uuid, - 'path' => $file, - ], 5); - - return redirect($server->node->scheme . '://' . $server->node->fqdn . ':' . $server->node->daemonListen . '/server/file/download/' . $token); - } - /** * Returns the allocation overview for a server. * diff --git a/app/Http/Requests/Server/UpdateFileContentsFormRequest.php b/app/Http/Requests/Server/UpdateFileContentsFormRequest.php new file mode 100644 index 000000000..cde7d8a08 --- /dev/null +++ b/app/Http/Requests/Server/UpdateFileContentsFormRequest.php @@ -0,0 +1,127 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\Requests\Server; + +use GuzzleHttp\Exception\RequestException; +use Illuminate\Contracts\Config\Repository; +use Illuminate\Contracts\Session\Session; +use Illuminate\Log\Writer; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException; +use Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException; +use Pterodactyl\Http\Requests\FrontendUserFormRequest; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; + +class UpdateFileContentsFormRequest extends FrontendUserFormRequest +{ + /** + * @var object + */ + protected $stats; + + /** + * Authorize a request to edit a file. + * + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException + * @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function authorize() + { + parent::authorize(); + + $session = app()->make(Session::class); + $server = $session->get('server_data.model'); + $token = $session->get('server_data.token'); + + $permission = $this->user()->can('edit-files', $server); + if (! $permission) { + return false; + } + + return $this->checkFileCanBeEdited($server, $token); + } + + /** + * @return array + */ + public function rules() + { + return []; + } + + /** + * Return the file stats from the Daemon. + * + * @return object + */ + public function getStats() + { + return $this->stats; + } + + /** + * @param \Pterodactyl\Models\Server $server + * @param string $token + * @return bool + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException + * @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + protected function checkFileCanBeEdited($server, $token) + { + $config = app()->make(Repository::class); + $repository = app()->make(FileRepositoryInterface::class); + + try { + $this->stats = $repository->setNode($server->node_id) + ->setAccessServer($server->uuid) + ->setAccessToken($token) + ->getFileStat($this->route()->parameter('file')); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + app()->make(Writer::class)->warning($exception); + + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + + if (! $this->stats->file || ! in_array($this->stats->mime, $config->get('pterodactyl.files.editable'))) { + throw new FileTypeNotEditableException(trans('server.files.exceptions.invalid_mime')); + } + + if ($this->stats->size > $config->get('pterodactyl.files.max_edit_size')) { + throw new FileSizeTooLargeException(trans('server.files.exceptions.max_size')); + } + + return true; + } +} diff --git a/app/Http/ViewComposers/Server/ServerDataComposer.php b/app/Http/ViewComposers/Server/ServerDataComposer.php new file mode 100644 index 000000000..93bdb1bf3 --- /dev/null +++ b/app/Http/ViewComposers/Server/ServerDataComposer.php @@ -0,0 +1,60 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Http\ViewComposers\Server; + +use Illuminate\View\View; +use Illuminate\Contracts\Session\Session; + +class ServerDataComposer +{ + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * ServerDataComposer constructor. + * + * @param \Illuminate\Contracts\Session\Session $session + */ + public function __construct(Session $session) + { + $this->session = $session; + } + + /** + * Attach server data to a view automatically. + * + * @param \Illuminate\View\View $view + */ + public function compose(View $view) + { + $data = $this->session->get('server_data'); + + $view->with('server', array_get($data, 'model')); + $view->with('node', object_get($data['model'], 'node')); + $view->with('daemon_token', array_get($data, 'token')); + } +} diff --git a/app/Providers/ViewComposerServiceProvider.php b/app/Providers/ViewComposerServiceProvider.php new file mode 100644 index 000000000..df1648f1a --- /dev/null +++ b/app/Providers/ViewComposerServiceProvider.php @@ -0,0 +1,39 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Providers; + +use Illuminate\Support\ServiceProvider; +use Pterodactyl\Http\ViewComposers\Server\ServerDataComposer; + +class ViewComposerServiceProvider extends ServiceProvider +{ + /** + * Register bindings in the container. + */ + public function boot() + { + $this->app->make('view')->composer('server.*', ServerDataComposer::class); + } +} diff --git a/app/Repositories/Daemon/FileRepository.php b/app/Repositories/Daemon/FileRepository.php index 30e73c821..ee1733310 100644 --- a/app/Repositories/Daemon/FileRepository.php +++ b/app/Repositories/Daemon/FileRepository.php @@ -59,7 +59,7 @@ class FileRepository extends BaseRepository implements FileRepositoryInterface rawurlencode($file['dirname'] . $file['basename']) )); - return json_decode($response->getBody()); + return object_get(json_decode($response->getBody()), 'content'); } /** diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index abf31079d..9ff713c8f 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -78,7 +78,7 @@ class AssignmentService $explode = explode('/', $data['allocation_ip']); if (count($explode) !== 1) { if (! ctype_digit($explode[1]) || ($explode[1] > self::CIDR_MIN_BITS || $explode[1] < self::CIDR_MAX_BITS)) { - throw new DisplayException(trans('admin/exceptions.allocations.cidr_out_of_range')); + throw new DisplayException(trans('exceptions.allocations.cidr_out_of_range')); } } @@ -86,7 +86,7 @@ class AssignmentService foreach (Network::parse(gethostbyname($data['allocation_ip'])) as $ip) { foreach ($data['allocation_ports'] as $port) { if (! ctype_digit($port) && ! preg_match(self::PORT_RANGE_REGEX, $port)) { - throw new DisplayException(trans('admin/exceptions.allocations.invalid_mapping', ['port' => $port])); + throw new DisplayException(trans('exceptions.allocations.invalid_mapping', ['port' => $port])); } $insertData = []; @@ -94,7 +94,7 @@ class AssignmentService $block = range($matches[1], $matches[2]); if (count($block) > self::PORT_RANGE_LIMIT) { - throw new DisplayException(trans('admin/exceptions.allocations.too_many_ports')); + throw new DisplayException(trans('exceptions.allocations.too_many_ports')); } foreach ($block as $unit) { diff --git a/app/Services/Database/DatabaseHostService.php b/app/Services/Database/DatabaseHostService.php index f64f3a941..10e45e53c 100644 --- a/app/Services/Database/DatabaseHostService.php +++ b/app/Services/Database/DatabaseHostService.php @@ -155,7 +155,7 @@ class DatabaseHostService { $count = $this->databaseRepository->findCountWhere([['database_host_id', '=', $id]]); if ($count > 0) { - throw new DisplayException(trans('admin/exceptions.databases.delete_has_databases')); + throw new DisplayException(trans('exceptions.databases.delete_has_databases')); } return $this->repository->delete($id); diff --git a/app/Services/Nodes/NodeDeletionService.php b/app/Services/Nodes/NodeDeletionService.php index 1a7868227..26238339b 100644 --- a/app/Services/Nodes/NodeDeletionService.php +++ b/app/Services/Nodes/NodeDeletionService.php @@ -80,7 +80,7 @@ class NodeDeletionService $servers = $this->serverRepository->withColumns('id')->findCountWhere([['node_id', '=', $node]]); if ($servers > 0) { - throw new HasActiveServersException($this->translator->trans('admin/exceptions.node.servers_attached')); + throw new HasActiveServersException($this->translator->trans('exceptions.node.servers_attached')); } return $this->repository->delete($node); diff --git a/app/Services/Nodes/NodeUpdateService.php b/app/Services/Nodes/NodeUpdateService.php index 199d72f31..9fd27332d 100644 --- a/app/Services/Nodes/NodeUpdateService.php +++ b/app/Services/Nodes/NodeUpdateService.php @@ -95,7 +95,7 @@ class NodeUpdateService $response = $exception->getResponse(); $this->writer->warning($exception); - throw new DisplayException(trans('admin/exceptions.node.daemon_off_config_updated', [ + throw new DisplayException(trans('exceptions.node.daemon_off_config_updated', [ 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), ])); } diff --git a/app/Services/Packs/PackCreationService.php b/app/Services/Packs/PackCreationService.php index b42740237..3943d122b 100644 --- a/app/Services/Packs/PackCreationService.php +++ b/app/Services/Packs/PackCreationService.php @@ -86,11 +86,11 @@ class PackCreationService { if (! is_null($file)) { if (! $file->isValid()) { - throw new InvalidFileUploadException(trans('admin/exceptions.packs.invalid_upload')); + throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload')); } if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { - throw new InvalidFileMimeTypeException(trans('admin/exceptions.packs.invalid_mime', [ + throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [ 'type' => implode(', ', self::VALID_UPLOAD_TYPES), ])); } diff --git a/app/Services/Packs/PackDeletionService.php b/app/Services/Packs/PackDeletionService.php index 590bdb4db..c288a3d04 100644 --- a/app/Services/Packs/PackDeletionService.php +++ b/app/Services/Packs/PackDeletionService.php @@ -89,7 +89,7 @@ class PackDeletionService $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); if ($count !== 0) { - throw new HasActiveServersException(trans('admin/exceptions.packs.delete_has_servers')); + throw new HasActiveServersException(trans('exceptions.packs.delete_has_servers')); } $this->connection->beginTransaction(); diff --git a/app/Services/Packs/PackUpdateService.php b/app/Services/Packs/PackUpdateService.php index 5928b95ac..f03903767 100644 --- a/app/Services/Packs/PackUpdateService.php +++ b/app/Services/Packs/PackUpdateService.php @@ -76,7 +76,7 @@ class PackUpdateService $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); if ($count !== 0) { - throw new HasActiveServersException(trans('admin/exceptions.packs.update_has_servers')); + throw new HasActiveServersException(trans('exceptions.packs.update_has_servers')); } } diff --git a/app/Services/Packs/TemplateUploadService.php b/app/Services/Packs/TemplateUploadService.php index 248ccfcf2..131757361 100644 --- a/app/Services/Packs/TemplateUploadService.php +++ b/app/Services/Packs/TemplateUploadService.php @@ -81,11 +81,11 @@ class TemplateUploadService public function handle($option, UploadedFile $file) { if (! $file->isValid()) { - throw new InvalidFileUploadException(trans('admin/exceptions.packs.invalid_upload')); + throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload')); } if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { - throw new InvalidFileMimeTypeException(trans('admin/exceptions.packs.invalid_mime', [ + throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [ 'type' => implode(', ', self::VALID_UPLOAD_TYPES), ])); } @@ -117,11 +117,11 @@ class TemplateUploadService protected function handleArchive($option, $file) { if (! $this->archive->open($file->getRealPath())) { - throw new UnreadableZipArchiveException(trans('admin/exceptions.packs.unreadable')); + throw new UnreadableZipArchiveException(trans('exceptions.packs.unreadable')); } if (! $this->archive->locateName('import.json') || ! $this->archive->locateName('archive.tar.gz')) { - throw new InvalidPackArchiveFormatException(trans('admin/exceptions.packs.invalid_archive_exception')); + throw new InvalidPackArchiveFormatException(trans('exceptions.packs.invalid_archive_exception')); } $json = json_decode($this->archive->getFromName('import.json'), true); @@ -130,7 +130,7 @@ class TemplateUploadService $pack = $this->creationService->handle($json); if (! $this->archive->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) { // @todo delete the pack that was created. - throw new ZipExtractionException(trans('admin/exceptions.packs.zip_extraction')); + throw new ZipExtractionException(trans('exceptions.packs.zip_extraction')); } $this->archive->close(); diff --git a/app/Services/Services/Options/InstallScriptUpdateService.php b/app/Services/Services/Options/InstallScriptUpdateService.php index efdd08dfe..976c21446 100644 --- a/app/Services/Services/Options/InstallScriptUpdateService.php +++ b/app/Services/Services/Options/InstallScriptUpdateService.php @@ -63,7 +63,7 @@ class InstallScriptUpdateService if (! is_null(array_get($data, 'copy_script_from'))) { if (! $this->repository->isCopiableScript(array_get($data, 'copy_script_from'), $option->service_id)) { - throw new InvalidCopyFromException(trans('admin/exceptions.service.options.invalid_copy_id')); + throw new InvalidCopyFromException(trans('exceptions.service.options.invalid_copy_id')); } } diff --git a/app/Services/Services/Options/OptionCreationService.php b/app/Services/Services/Options/OptionCreationService.php index 8755f0e9d..509f64c16 100644 --- a/app/Services/Services/Options/OptionCreationService.php +++ b/app/Services/Services/Options/OptionCreationService.php @@ -62,7 +62,7 @@ class OptionCreationService ]); if ($results !== 1) { - throw new NoParentConfigurationFoundException(trans('admin/exceptions.service.options.must_be_child')); + throw new NoParentConfigurationFoundException(trans('exceptions.service.options.must_be_child')); } } else { $data['config_from'] = null; diff --git a/app/Services/Services/Options/OptionDeletionService.php b/app/Services/Services/Options/OptionDeletionService.php index c614348ff..02a1e734e 100644 --- a/app/Services/Services/Options/OptionDeletionService.php +++ b/app/Services/Services/Options/OptionDeletionService.php @@ -69,7 +69,7 @@ class OptionDeletionService ]); if ($servers > 0) { - throw new HasActiveServersException(trans('admin/exceptions.service.options.delete_has_servers')); + throw new HasActiveServersException(trans('exceptions.service.options.delete_has_servers')); } return $this->repository->delete($option); diff --git a/app/Services/Services/Options/OptionUpdateService.php b/app/Services/Services/Options/OptionUpdateService.php index 6bfe634e3..62bf5d393 100644 --- a/app/Services/Services/Options/OptionUpdateService.php +++ b/app/Services/Services/Options/OptionUpdateService.php @@ -68,7 +68,7 @@ class OptionUpdateService ]); if ($results !== 1) { - throw new NoParentConfigurationFoundException(trans('admin/exceptions.service.options.must_be_child')); + throw new NoParentConfigurationFoundException(trans('exceptions.service.options.must_be_child')); } } diff --git a/app/Services/Services/ServiceDeletionService.php b/app/Services/Services/ServiceDeletionService.php index 9a299beeb..27e291ed3 100644 --- a/app/Services/Services/ServiceDeletionService.php +++ b/app/Services/Services/ServiceDeletionService.php @@ -66,7 +66,7 @@ class ServiceDeletionService { $count = $this->serverRepository->findCountWhere([['service_id', '=', $service]]); if ($count > 0) { - throw new HasActiveServersException(trans('admin/exceptions.service.delete_has_servers')); + throw new HasActiveServersException(trans('exceptions.service.delete_has_servers')); } return $this->repository->delete($service); diff --git a/app/Services/Services/Variables/VariableUpdateService.php b/app/Services/Services/Variables/VariableUpdateService.php index 1806c11c3..4792ad6bb 100644 --- a/app/Services/Services/Variables/VariableUpdateService.php +++ b/app/Services/Services/Variables/VariableUpdateService.php @@ -66,7 +66,7 @@ class VariableUpdateService if (! is_null(array_get($data, 'env_variable'))) { if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', ServiceVariable::RESERVED_ENV_NAMES))) { - throw new ReservedVariableNameException(trans('admin/exceptions.service.variables.reserved_name', [ + throw new ReservedVariableNameException(trans('exceptions.service.variables.reserved_name', [ 'name' => array_get($data, 'env_variable'), ])); } @@ -78,7 +78,7 @@ class VariableUpdateService ]); if ($search > 0) { - throw new DisplayException(trans('admin/exceptions.service.variables.env_not_unique', [ + throw new DisplayException(trans('exceptions.service.variables.env_not_unique', [ 'name' => array_get($data, 'env_variable'), ])); } diff --git a/app/Services/Subusers/SubuserCreationService.php b/app/Services/Subusers/SubuserCreationService.php index 78d99afb7..55cb6e906 100644 --- a/app/Services/Subusers/SubuserCreationService.php +++ b/app/Services/Subusers/SubuserCreationService.php @@ -131,12 +131,12 @@ class SubuserCreationService ]); } else { if ($server->owner_id === $user->id) { - throw new UserIsServerOwnerException(trans('admin/exceptions.subusers.user_is_owner')); + throw new UserIsServerOwnerException(trans('exceptions.subusers.user_is_owner')); } $subuserCount = $this->subuserRepository->findCountWhere([['user_id', '=', $user->id], ['server_id', '=', $server->id]]); if ($subuserCount !== 0) { - throw new ServerSubuserExistsException(trans('admin/exceptions.subusers.subuser_exists')); + throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists')); } } @@ -160,7 +160,7 @@ class SubuserCreationService $this->writer->warning($exception); $response = $exception->getResponse(); - throw new DisplayException(trans('admin/exceptions.daemon_connection_failed', [ + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), ])); } diff --git a/app/Services/Subusers/SubuserDeletionService.php b/app/Services/Subusers/SubuserDeletionService.php index 2cbc168b0..97b723a50 100644 --- a/app/Services/Subusers/SubuserDeletionService.php +++ b/app/Services/Subusers/SubuserDeletionService.php @@ -100,7 +100,7 @@ class SubuserDeletionService $this->writer->warning($exception); $response = $exception->getResponse(); - throw new DisplayException(trans('admin/exceptions.daemon_connection_failed', [ + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), ])); } diff --git a/app/Services/Subusers/SubuserUpdateService.php b/app/Services/Subusers/SubuserUpdateService.php index 11faf5bb5..c11c551e9 100644 --- a/app/Services/Subusers/SubuserUpdateService.php +++ b/app/Services/Subusers/SubuserUpdateService.php @@ -117,7 +117,7 @@ class SubuserUpdateService $this->writer->warning($exception); $response = $exception->getResponse(); - throw new DisplayException(trans('admin/exceptions.daemon_connection_failed', [ + throw new DisplayException(trans('exceptions.daemon_connection_failed', [ 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), ])); } diff --git a/app/Traits/Controllers/ServerToJavascript.php b/app/Traits/Controllers/JavascriptInjection.php similarity index 98% rename from app/Traits/Controllers/ServerToJavascript.php rename to app/Traits/Controllers/JavascriptInjection.php index 6c550f58f..5a8f0b337 100644 --- a/app/Traits/Controllers/ServerToJavascript.php +++ b/app/Traits/Controllers/JavascriptInjection.php @@ -26,7 +26,7 @@ namespace Pterodactyl\Traits\Controllers; use Javascript; -trait ServerToJavascript +trait JavascriptInjection { /** * @var \Illuminate\Contracts\Session\Session diff --git a/config/app.php b/config/app.php index af3cf02fc..b0e36cbca 100644 --- a/config/app.php +++ b/config/app.php @@ -165,6 +165,7 @@ return [ Pterodactyl\Providers\MacroServiceProvider::class, Pterodactyl\Providers\PhraseAppTranslationProvider::class, Pterodactyl\Providers\RepositoryServiceProvider::class, + Pterodactyl\Providers\ViewComposerServiceProvider::class, /* * Additional Dependencies diff --git a/resources/lang/en/admin/exceptions.php b/resources/lang/en/exceptions.php similarity index 100% rename from resources/lang/en/admin/exceptions.php rename to resources/lang/en/exceptions.php diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php index bb852965a..07e8faa2b 100644 --- a/resources/lang/en/server.php +++ b/resources/lang/en/server.php @@ -203,6 +203,11 @@ return [ ], ], 'files' => [ + 'exceptions' => [ + 'invalid_mime' => 'This type of file cannot be edited via the Panel\'s built-in editor.', + 'max_size' => 'This file is too large to edit via the Panel\'s built-in editor.', + 'list_directory' => 'An error was encountered while attempting to get the contents of this directory. Please try again.', + ], 'header' => 'File Manager', 'header_sub' => 'Manage all of your files directly from the web.', 'loading' => 'Loading initial file structure, this could take a few seconds.', diff --git a/routes/server.php b/routes/server.php index f8e68ee62..46724e52c 100644 --- a/routes/server.php +++ b/routes/server.php @@ -51,17 +51,13 @@ Route::group(['prefix' => 'settings'], function () { | */ Route::group(['prefix' => 'files'], function () { - Route::get('/', 'ServerController@getFiles')->name('server.files.index'); - Route::get('/add', 'ServerController@getAddFile')->name('server.files.add'); - Route::get('/edit/{file}', 'ServerController@getEditFile') - ->name('server.files.edit') - ->where('file', '.*'); - Route::get('/download/{file}', 'ServerController@getDownloadFile') - ->name('server.files.edit') - ->where('file', '.*'); + Route::get('/', 'Files\FileActionsController@index')->name('server.files.index'); + Route::get('/add', 'Files\FileActionsController@create')->name('server.files.add'); + Route::get('/edit/{file}', 'Files\FileActionsController@update')->name('server.files.edit')->where('file', '.*'); + Route::get('/download/{file}', 'Files\DownloadController@index')->name('server.files.edit')->where('file', '.*'); - Route::post('/directory-list', 'AjaxController@postDirectoryList')->name('server.files.directory-list'); - Route::post('/save', 'AjaxController@postSaveFile')->name('server.files.save'); + Route::post('/directory-list', 'Files\RemoteRequestController@directory')->name('server.files.directory-list'); + Route::post('/save', 'Files\RemoteRequestController@store')->name('server.files.save'); }); /* diff --git a/tests/Assertions/ControllerAssertionsTrait.php b/tests/Assertions/ControllerAssertionsTrait.php index a35588380..420eba1be 100644 --- a/tests/Assertions/ControllerAssertionsTrait.php +++ b/tests/Assertions/ControllerAssertionsTrait.php @@ -160,11 +160,22 @@ trait ControllerAssertionsTrait * @param string $route * @param mixed $response */ - public function assertRouteRedirectEquals($route, $response) + public function assertRedirectRouteEquals($route, $response) { PHPUnit_Framework_Assert::assertEquals(route($route), $response->getTargetUrl()); } + /** + * Assert that a route redirect URL equals as passed URL. + * + * @param string $url + * @param mixed $response + */ + public function assertRedirectUrlEquals($url, $response) + { + PHPUnit_Framework_Assert::assertEquals($url, $response->getTargetUrl()); + } + /** * Assert that a response code equals a given code. * diff --git a/tests/Unit/Http/Controllers/Base/APIControllerTest.php b/tests/Unit/Http/Controllers/Base/APIControllerTest.php index cc2c1302e..19f0e6dc3 100644 --- a/tests/Unit/Http/Controllers/Base/APIControllerTest.php +++ b/tests/Unit/Http/Controllers/Base/APIControllerTest.php @@ -149,7 +149,7 @@ class APIControllerTest extends TestCase $response = $this->controller->store($this->request); $this->assertIsRedirectResponse($response); - $this->assertRouteRedirectEquals('account.api', $response); + $this->assertRedirectRouteEquals('account.api', $response); } /** diff --git a/tests/Unit/Http/Controllers/Base/AccountControllerTest.php b/tests/Unit/Http/Controllers/Base/AccountControllerTest.php index 7d8258b0b..6f277ba2c 100644 --- a/tests/Unit/Http/Controllers/Base/AccountControllerTest.php +++ b/tests/Unit/Http/Controllers/Base/AccountControllerTest.php @@ -95,7 +95,7 @@ class AccountControllerTest extends TestCase $response = $this->controller->update($this->request); $this->assertIsRedirectResponse($response); - $this->assertRouteRedirectEquals('account', $response); + $this->assertRedirectRouteEquals('account', $response); } /** @@ -112,7 +112,7 @@ class AccountControllerTest extends TestCase $response = $this->controller->update($this->request); $this->assertIsRedirectResponse($response); - $this->assertRouteRedirectEquals('account', $response); + $this->assertRedirectRouteEquals('account', $response); } /** @@ -131,6 +131,6 @@ class AccountControllerTest extends TestCase $response = $this->controller->update($this->request); $this->assertIsRedirectResponse($response); - $this->assertRouteRedirectEquals('account', $response); + $this->assertRedirectRouteEquals('account', $response); } } diff --git a/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php b/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php index 04186b390..359b626f4 100644 --- a/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php +++ b/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php @@ -167,7 +167,7 @@ class SecurityControllerTest extends TestCase $response = $this->controller->disableTotp($this->request); $this->assertIsRedirectResponse($response); - $this->assertRouteRedirectEquals('account.security', $response); + $this->assertRedirectRouteEquals('account.security', $response); } /** @@ -186,7 +186,7 @@ class SecurityControllerTest extends TestCase $response = $this->controller->disableTotp($this->request); $this->assertIsRedirectResponse($response); - $this->assertRouteRedirectEquals('account.security', $response); + $this->assertRedirectRouteEquals('account.security', $response); } /** @@ -201,6 +201,6 @@ class SecurityControllerTest extends TestCase $response = $this->controller->revoke($this->request, 123); $this->assertIsRedirectResponse($response); - $this->assertRouteRedirectEquals('account.security', $response); + $this->assertRedirectRouteEquals('account.security', $response); } } diff --git a/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php b/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php index e56c4ea7b..b89dbb5bf 100644 --- a/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php +++ b/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php @@ -27,7 +27,6 @@ namespace Tests\Unit\Http\Controllers\Server; use Illuminate\Contracts\Session\Session; use Mockery as m; use Pterodactyl\Http\Controllers\Server\ConsoleController; -use Pterodactyl\Models\Node; use Pterodactyl\Models\Server; use Tests\Assertions\ControllerAssertionsTrait; use Tests\TestCase; @@ -73,10 +72,10 @@ class ConsoleControllerTest extends TestCase public function testAllControllers($function, $view) { $server = factory(Server::class)->make(); - $node = factory(Node::class)->make(); - $server->node = $node; - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); + if ($function === 'index') { + $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); + } $this->config->shouldReceive('get')->with('pterodactyl.console.count')->once()->andReturn(100); $this->config->shouldReceive('get')->with('pterodactyl.console.frequency')->once()->andReturn(10); $this->controller->shouldReceive('injectJavascript')->once()->andReturnNull(); @@ -84,10 +83,6 @@ class ConsoleControllerTest extends TestCase $response = $this->controller->$function(); $this->assertIsViewResponse($response); $this->assertViewNameEquals($view, $response); - $this->assertViewHasKey('server', $response); - $this->assertViewHasKey('node', $response); - $this->assertViewKeyEquals('server', $server, $response); - $this->assertViewKeyEquals('node', $node, $response); } /** diff --git a/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php b/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php new file mode 100644 index 000000000..fcf066e19 --- /dev/null +++ b/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php @@ -0,0 +1,92 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Unit\Http\Controllers\Server\Files; + +use Mockery as m; +use Illuminate\Cache\Repository; +use Illuminate\Contracts\Session\Session; +use phpmock\phpunit\PHPMock; +use Pterodactyl\Http\Controllers\Server\Files\DownloadController; +use Pterodactyl\Models\Node; +use Pterodactyl\Models\Server; +use Tests\Assertions\ControllerAssertionsTrait; +use Tests\TestCase; + +class DownloadControllerTest extends TestCase +{ + use ControllerAssertionsTrait, PHPMock; + + /** + * @var \Illuminate\Cache\Repository + */ + protected $cache; + + /** + * @var \Pterodactyl\Http\Controllers\Server\Files\DownloadController + */ + protected $controller; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->cache = m::mock(Repository::class); + $this->session = m::mock(Session::class); + + $this->controller = m::mock(DownloadController::class, [$this->cache, $this->session])->makePartial(); + } + + /** + * Test the download controller redirects correctly. + */ + public function testIndexController() + { + $server = factory(Server::class)->make(); + $node = factory(Node::class)->make(); + $server->node = $node; + + $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); + $this->controller->shouldReceive('authorize')->with('download-files', $server)->once()->andReturnNull(); + $this->getFunctionMock('\\Pterodactyl\\Http\\Controllers\\Server\\Files', 'str_random') + ->expects($this->once())->willReturn('randomString'); + + $this->cache->shouldReceive('tags')->with(['Server:Downloads'])->once()->andReturnSelf() + ->shouldReceive('put')->with('randomString', ['server' => $server->uuid, 'path' => '/my/file.txt'], 5)->once()->andReturnNull(); + + $response = $this->controller->index('1234', '/my/file.txt'); + $this->assertIsRedirectResponse($response); + $this->assertRedirectUrlEquals(sprintf( + '%s://%s:%s/server/file/download/%s', $server->node->scheme, $server->node->fqdn, $server->node->daemonListen, 'randomString' + ), $response); + } +} diff --git a/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php b/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php new file mode 100644 index 000000000..edbc6e3f8 --- /dev/null +++ b/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php @@ -0,0 +1,217 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Tests\Unit\Http\Controllers\Server\Files; + +use GuzzleHttp\Exception\RequestException; +use Illuminate\Contracts\Session\Session; +use Illuminate\Http\Request; +use Illuminate\Log\Writer; +use Mockery as m; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Server\Files\FileActionsController; +use Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest; +use Pterodactyl\Models\Server; +use Tests\Assertions\ControllerAssertionsTrait; +use Tests\TestCase; + +class FileActionsControllerTest extends TestCase +{ + use ControllerAssertionsTrait; + + /** + * @var \Pterodactyl\Http\Controllers\Server\Files\FileActionsController + */ + protected $controller; + + /** + * @var \Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest + */ + protected $fileContentsFormRequest; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface + */ + protected $fileRepository; + + /** + * @var \Illuminate\Http\Request + */ + protected $request; + + /** + * @var \Illuminate\Contracts\Session\Session + */ + protected $session; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->fileContentsFormRequest = m::mock(UpdateFileContentsFormRequest::class); + $this->fileRepository = m::mock(FileRepositoryInterface::class); + $this->request = m::mock(Request::class); + $this->session = m::mock(Session::class); + $this->writer = m::mock(Writer::class); + + $this->controller = m::mock(FileActionsController::class, [ + $this->fileRepository, $this->session, $this->writer, + ])->makePartial(); + } + + /** + * Test the index view controller. + */ + public function testIndexController() + { + $server = factory(Server::class)->make(); + + $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); + $this->controller->shouldReceive('authorize')->with('list-files', $server)->once()->andReturnNull(); + $this->request->shouldReceive('user->can')->andReturn(true); + $this->controller->shouldReceive('injectJavascript')->once()->andReturnNull(); + + $response = $this->controller->index($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.files.index', $response); + } + + /** + * Test the file creation view controller. + * + * @dataProvider directoryNameProvider + */ + public function testCreateController($directory, $expected) + { + $server = factory(Server::class)->make(); + + $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); + $this->controller->shouldReceive('authorize')->with('create-files', $server)->once()->andReturnNull(); + $this->controller->shouldReceive('injectJavascript')->once()->andReturnNull(); + $this->request->shouldReceive('get')->with('dir')->andReturn($directory); + + $response = $this->controller->create($this->request); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.files.add', $response); + $this->assertViewHasKey('directory', $response); + $this->assertViewKeyEquals('directory', $expected, $response); + } + + /** + * Test the update controller. + * + * @dataProvider fileNameProvider + */ + public function testUpdateController($file, $expected) + { + $server = factory(Server::class)->make(); + + $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); + $this->controller->shouldReceive('authorize')->with('edit-files', $server)->once()->andReturnNull(); + $this->session->shouldReceive('get')->with('server_data.token')->once()->andReturn($server->daemonSecret); + $this->fileRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() + ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() + ->shouldReceive('setAccessToken')->with($server->daemonSecret)->once()->andReturnSelf() + ->shouldReceive('getContent')->with($file)->once()->andReturn('file contents'); + + $this->fileContentsFormRequest->shouldReceive('getStats')->withNoArgs()->twice()->andReturn(['stats']); + $this->controller->shouldReceive('injectJavascript')->with(['stat' => ['stats']])->once()->andReturnNull(); + + $response = $this->controller->update($this->fileContentsFormRequest, '1234', $file); + $this->assertIsViewResponse($response); + $this->assertViewNameEquals('server.files.edit', $response); + $this->assertViewHasKey('file', $response); + $this->assertViewHasKey('stat', $response); + $this->assertViewHasKey('contents', $response); + $this->assertViewHasKey('directory', $response); + $this->assertViewKeyEquals('file', $file, $response); + $this->assertViewKeyEquals('stat', ['stats'], $response); + $this->assertViewKeyEquals('contents', 'file contents', $response); + $this->assertViewKeyEquals('directory', $expected, $response); + } + + /** + * Test that an exception is handled correctly in the controller. + */ + public function testExceptionRenderedByUpdateController() + { + $server = factory(Server::class)->make(); + $exception = m::mock(RequestException::class); + + $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); + $this->controller->shouldReceive('authorize')->with('edit-files', $server)->once()->andReturnNull(); + $this->fileRepository->shouldReceive('setNode')->with($server->node_id)->once()->andThrow($exception); + + $exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnNull(); + $this->writer->shouldReceive('warning')->with($exception)->once()->andReturnNull(); + + try { + $this->controller->update($this->fileContentsFormRequest, '1234', 'file.txt'); + } catch (DisplayException $exception) { + $this->assertEquals(trans('exceptions.daemon_connection_failed', ['code' => 'E_CONN_REFUSED']), $exception->getMessage()); + } + } + + /** + * Provides a list of directory names and the expected output from formatting. + * + * @return array + */ + public function directoryNameProvider() + { + return [ + [null, ''], + ['/', ''], + ['', ''], + ['my/directory', 'my/directory/'], + ['/my/directory/', 'my/directory/'], + ['/////my/directory////', 'my/directory/'], + ]; + } + + /** + * Provides a list of file names and the expected output from formatting. + * + * @return array + */ + public function fileNameProvider() + { + return [ + ['/my/file.txt', 'my/'], + ['my/file.txt', 'my/'], + ['file.txt', '/'], + ['/file.txt', '/'], + ['./file.txt', '/'], + ]; + } +} diff --git a/tests/Unit/Services/Allocations/AssignmentServiceTest.php b/tests/Unit/Services/Allocations/AssignmentServiceTest.php index f55fdc5f9..25352e376 100644 --- a/tests/Unit/Services/Allocations/AssignmentServiceTest.php +++ b/tests/Unit/Services/Allocations/AssignmentServiceTest.php @@ -247,7 +247,7 @@ class AssignmentServiceTest extends TestCase $this->service->handle($this->node->id, $data); } catch (Exception $exception) { $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals(trans('admin/exceptions.allocations.cidr_out_of_range'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.allocations.cidr_out_of_range'), $exception->getMessage()); } } @@ -271,7 +271,7 @@ class AssignmentServiceTest extends TestCase } $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals(trans('admin/exceptions.allocations.too_many_ports'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.allocations.too_many_ports'), $exception->getMessage()); } } @@ -295,7 +295,7 @@ class AssignmentServiceTest extends TestCase } $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals(trans('admin/exceptions.allocations.invalid_mapping', ['port' => 'test123']), $exception->getMessage()); + $this->assertEquals(trans('exceptions.allocations.invalid_mapping', ['port' => 'test123']), $exception->getMessage()); } } diff --git a/tests/Unit/Services/Database/DatabaseHostServiceTest.php b/tests/Unit/Services/Database/DatabaseHostServiceTest.php index bbba14537..d65ab8892 100644 --- a/tests/Unit/Services/Database/DatabaseHostServiceTest.php +++ b/tests/Unit/Services/Database/DatabaseHostServiceTest.php @@ -211,7 +211,7 @@ class DatabaseHostServiceTest extends TestCase try { $this->service->delete(1); } catch (DisplayException $exception) { - $this->assertEquals(trans('admin/exceptions.databases.delete_has_databases'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.databases.delete_has_databases'), $exception->getMessage()); } } } diff --git a/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php b/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php index 5f92d40fa..862bd3dab 100644 --- a/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php +++ b/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php @@ -96,7 +96,7 @@ class NodeDeletionServiceTest extends TestCase { $this->serverRepository->shouldReceive('withColumns')->with('id')->once()->andReturnSelf() ->shouldReceive('findCountWhere')->with([['node_id', '=', 1]])->once()->andReturn(1); - $this->translator->shouldReceive('trans')->with('admin/exceptions.node.servers_attached')->once()->andReturnNull(); + $this->translator->shouldReceive('trans')->with('exceptions.node.servers_attached')->once()->andReturnNull(); $this->repository->shouldNotReceive('delete'); $this->service->handle(1); diff --git a/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php b/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php index 386e06fa2..b5c1c4869 100644 --- a/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php +++ b/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php @@ -157,7 +157,7 @@ class NodeUpdateServiceTest extends TestCase } catch (Exception $exception) { $this->assertInstanceOf(DisplayException::class, $exception); $this->assertEquals( - trans('admin/exceptions.node.daemon_off_config_updated', ['code' => 400]), + trans('exceptions.node.daemon_off_config_updated', ['code' => 400]), $exception->getMessage() ); } diff --git a/tests/Unit/Services/Packs/PackCreationServiceTest.php b/tests/Unit/Services/Packs/PackCreationServiceTest.php index 9dc634baf..46706d331 100644 --- a/tests/Unit/Services/Packs/PackCreationServiceTest.php +++ b/tests/Unit/Services/Packs/PackCreationServiceTest.php @@ -152,7 +152,7 @@ class PackCreationServiceTest extends TestCase $this->service->handle([], $this->file); } catch (Exception $exception) { $this->assertInstanceOf(InvalidFileUploadException::class, $exception); - $this->assertEquals(trans('admin/exceptions.packs.invalid_upload'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.packs.invalid_upload'), $exception->getMessage()); } } @@ -169,7 +169,7 @@ class PackCreationServiceTest extends TestCase try { $this->service->handle([], $this->file); } catch (InvalidFileMimeTypeException $exception) { - $this->assertEquals(trans('admin/exceptions.packs.invalid_mime', [ + $this->assertEquals(trans('exceptions.packs.invalid_mime', [ 'type' => implode(', ', PackCreationService::VALID_UPLOAD_TYPES), ]), $exception->getMessage()); } diff --git a/tests/Unit/Services/Packs/PackDeletionServiceTest.php b/tests/Unit/Services/Packs/PackDeletionServiceTest.php index c6b2e4b3a..b345f2ace 100644 --- a/tests/Unit/Services/Packs/PackDeletionServiceTest.php +++ b/tests/Unit/Services/Packs/PackDeletionServiceTest.php @@ -130,7 +130,7 @@ class PackDeletionServiceTest extends TestCase try { $this->service->handle($model); } catch (HasActiveServersException $exception) { - $this->assertEquals(trans('admin/exceptions.packs.delete_has_servers'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.packs.delete_has_servers'), $exception->getMessage()); } } } diff --git a/tests/Unit/Services/Packs/PackUpdateServiceTest.php b/tests/Unit/Services/Packs/PackUpdateServiceTest.php index 48ae8d779..bd93d602f 100644 --- a/tests/Unit/Services/Packs/PackUpdateServiceTest.php +++ b/tests/Unit/Services/Packs/PackUpdateServiceTest.php @@ -90,7 +90,7 @@ class PackUpdateServiceTest extends TestCase try { $this->service->handle($model, ['option_id' => 0]); } catch (HasActiveServersException $exception) { - $this->assertEquals(trans('admin/exceptions.packs.update_has_servers'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.packs.update_has_servers'), $exception->getMessage()); } } diff --git a/tests/Unit/Services/Packs/TemplateUploadServiceTest.php b/tests/Unit/Services/Packs/TemplateUploadServiceTest.php index bb1a564f0..b77818c45 100644 --- a/tests/Unit/Services/Packs/TemplateUploadServiceTest.php +++ b/tests/Unit/Services/Packs/TemplateUploadServiceTest.php @@ -128,7 +128,7 @@ class TemplateUploadServiceTest extends TestCase try { $this->service->handle(1, $this->file); } catch (InvalidFileUploadException $exception) { - $this->assertEquals(trans('admin/exceptions.packs.invalid_upload'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.packs.invalid_upload'), $exception->getMessage()); } } @@ -145,7 +145,7 @@ class TemplateUploadServiceTest extends TestCase try { $this->service->handle(1, $this->file); } catch (InvalidFileMimeTypeException $exception) { - $this->assertEquals(trans('admin/exceptions.packs.invalid_mime', [ + $this->assertEquals(trans('exceptions.packs.invalid_mime', [ 'type' => implode(', ', TemplateUploadService::VALID_UPLOAD_TYPES), ]), $exception->getMessage()); } @@ -165,7 +165,7 @@ class TemplateUploadServiceTest extends TestCase try { $this->service->handle(1, $this->file); } catch (UnreadableZipArchiveException $exception) { - $this->assertEquals(trans('admin/exceptions.packs.unreadable'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.packs.unreadable'), $exception->getMessage()); } } @@ -190,7 +190,7 @@ class TemplateUploadServiceTest extends TestCase try { $this->service->handle(1, $this->file); } catch (InvalidPackArchiveFormatException $exception) { - $this->assertEquals(trans('admin/exceptions.packs.invalid_archive_exception'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.packs.invalid_archive_exception'), $exception->getMessage()); } } @@ -214,7 +214,7 @@ class TemplateUploadServiceTest extends TestCase try { $this->service->handle(1, $this->file); } catch (ZipExtractionException $exception) { - $this->assertEquals(trans('admin/exceptions.packs.zip_extraction'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.packs.zip_extraction'), $exception->getMessage()); } } diff --git a/tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php b/tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php index 4bb12a460..1308ac4f1 100644 --- a/tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php +++ b/tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php @@ -99,7 +99,7 @@ class InstallScriptUpdateServiceTest extends TestCase $this->service->handle($this->model, $this->data); } catch (Exception $exception) { $this->assertInstanceOf(InvalidCopyFromException::class, $exception); - $this->assertEquals(trans('admin/exceptions.service.options.invalid_copy_id'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.service.options.invalid_copy_id'), $exception->getMessage()); } } diff --git a/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php b/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php index 1d864b57f..025223128 100644 --- a/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php +++ b/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php @@ -116,7 +116,7 @@ class OptionCreationServiceTest extends TestCase $this->service->handle(['config_from' => 1]); } catch (Exception $exception) { $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); - $this->assertEquals(trans('admin/exceptions.service.options.must_be_child'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.service.options.must_be_child'), $exception->getMessage()); } } } diff --git a/tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php b/tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php index 381ac3a5d..21b59efc3 100644 --- a/tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php +++ b/tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php @@ -80,7 +80,7 @@ class OptionDeletionServiceTest extends TestCase $this->service->handle(1); } catch (\Exception $exception) { $this->assertInstanceOf(HasActiveServersException::class, $exception); - $this->assertEquals(trans('admin/exceptions.service.options.delete_has_servers'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.service.options.delete_has_servers'), $exception->getMessage()); } } } diff --git a/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php b/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php index ecc5f76c9..842075e9c 100644 --- a/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php +++ b/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php @@ -103,7 +103,7 @@ class OptionUpdateServiceTest extends TestCase $this->service->handle($this->model, ['config_from' => 1]); } catch (Exception $exception) { $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); - $this->assertEquals(trans('admin/exceptions.service.options.must_be_child'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.service.options.must_be_child'), $exception->getMessage()); } } diff --git a/tests/Unit/Services/Services/ServiceDeletionServiceTest.php b/tests/Unit/Services/Services/ServiceDeletionServiceTest.php index f986de519..d875b9494 100644 --- a/tests/Unit/Services/Services/ServiceDeletionServiceTest.php +++ b/tests/Unit/Services/Services/ServiceDeletionServiceTest.php @@ -86,7 +86,7 @@ class ServiceDeletionServiceTest extends TestCase $this->service->handle(1); } catch (Exception $exception) { $this->assertInstanceOf(HasActiveServersException::class, $exception); - $this->assertEquals(trans('admin/exceptions.service.delete_has_servers'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.service.delete_has_servers'), $exception->getMessage()); } } diff --git a/tests/Unit/Services/Services/Variables/VariableUpdateServiceTest.php b/tests/Unit/Services/Services/Variables/VariableUpdateServiceTest.php index 905389ef0..848ebdceb 100644 --- a/tests/Unit/Services/Services/Variables/VariableUpdateServiceTest.php +++ b/tests/Unit/Services/Services/Variables/VariableUpdateServiceTest.php @@ -116,7 +116,7 @@ class VariableUpdateServiceTest extends TestCase $this->service->handle($this->model, ['env_variable' => 'TEST_VAR_123']); } catch (Exception $exception) { $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals(trans('admin/exceptions.service.variables.env_not_unique', [ + $this->assertEquals(trans('exceptions.service.variables.env_not_unique', [ 'name' => 'TEST_VAR_123', ]), $exception->getMessage()); } diff --git a/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php b/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php index 42bae6984..ad29c866c 100644 --- a/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php +++ b/tests/Unit/Services/Subusers/SubuserCreationServiceTest.php @@ -213,7 +213,7 @@ class SubuserCreationServiceTest extends TestCase $this->service->handle($server, $user->email, []); } catch (DisplayException $exception) { $this->assertInstanceOf(UserIsServerOwnerException::class, $exception); - $this->assertEquals(trans('admin/exceptions.subusers.user_is_owner'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.subusers.user_is_owner'), $exception->getMessage()); } } @@ -235,7 +235,7 @@ class SubuserCreationServiceTest extends TestCase $this->service->handle($server, $user->email, []); } catch (DisplayException $exception) { $this->assertInstanceOf(ServerSubuserExistsException::class, $exception); - $this->assertEquals(trans('admin/exceptions.subusers.subuser_exists'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.subusers.subuser_exists'), $exception->getMessage()); } } } diff --git a/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php b/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php index 25a976e47..dc2d16efd 100644 --- a/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php +++ b/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php @@ -132,7 +132,7 @@ class SubuserDeletionServiceTest extends TestCase try { $this->service->handle($subuser->id); } catch (DisplayException $exception) { - $this->assertEquals(trans('admin/exceptions.daemon_connection_failed', ['code' => 'E_CONN_REFUSED']), $exception->getMessage()); + $this->assertEquals(trans('exceptions.daemon_connection_failed', ['code' => 'E_CONN_REFUSED']), $exception->getMessage()); } } } diff --git a/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php b/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php index c053d57e7..521af1090 100644 --- a/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php +++ b/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php @@ -152,7 +152,7 @@ class SubuserUpdateServiceTest extends TestCase try { $this->service->handle($subuser->id, []); } catch (DisplayException $exception) { - $this->assertEquals(trans('admin/exceptions.daemon_connection_failed', ['code' => 'E_CONN_REFUSED']), $exception->getMessage()); + $this->assertEquals(trans('exceptions.daemon_connection_failed', ['code' => 'E_CONN_REFUSED']), $exception->getMessage()); } } }