From 280633b28ae417f9329bcc0df5a5ce37a4a898f2 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 19 Aug 2017 20:40:00 -0500 Subject: [PATCH] More service classes for pack management --- .../Pack/InvalidFileMimeTypeException.php | 4 +- .../Pack/InvalidFileUploadException.php | 4 +- .../InvalidPackArchiveFormatException.php | 32 ++++ .../Pack/UnreadableZipArchiveException.php | 32 ++++ .../Service/Pack/ZipExtractionException.php | 31 ++++ app/Http/Controllers/Admin/PackController.php | 120 ++++++++++----- app/Services/Packs/PackCreationService.php | 25 ++-- app/Services/Packs/PackDeletionService.php | 100 +++++++++++++ app/Services/Packs/PackUpdateService.php | 90 +++++++++++ app/Services/Packs/TemplateUploadService.php | 140 ++++++++++++++++++ config/pterodactyl.php | 4 - resources/lang/en/admin/exceptions.php | 9 ++ resources/lang/en/admin/pack.php | 29 ++++ 13 files changed, 568 insertions(+), 52 deletions(-) create mode 100644 app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php create mode 100644 app/Exceptions/Service/Pack/UnreadableZipArchiveException.php create mode 100644 app/Exceptions/Service/Pack/ZipExtractionException.php create mode 100644 app/Services/Packs/PackDeletionService.php create mode 100644 app/Services/Packs/PackUpdateService.php create mode 100644 app/Services/Packs/TemplateUploadService.php create mode 100644 resources/lang/en/admin/pack.php diff --git a/app/Exceptions/Service/Pack/InvalidFileMimeTypeException.php b/app/Exceptions/Service/Pack/InvalidFileMimeTypeException.php index f34e1be89..bbd5d4107 100644 --- a/app/Exceptions/Service/Pack/InvalidFileMimeTypeException.php +++ b/app/Exceptions/Service/Pack/InvalidFileMimeTypeException.php @@ -24,7 +24,9 @@ namespace Pterodactyl\Exceptions\Service\Pack; -class InvalidFileMimeTypeException extends \Exception +use Pterodactyl\Exceptions\DisplayException; + +class InvalidFileMimeTypeException extends DisplayException { // } diff --git a/app/Exceptions/Service/Pack/InvalidFileUploadException.php b/app/Exceptions/Service/Pack/InvalidFileUploadException.php index ffef85b8c..4861512c2 100644 --- a/app/Exceptions/Service/Pack/InvalidFileUploadException.php +++ b/app/Exceptions/Service/Pack/InvalidFileUploadException.php @@ -24,7 +24,9 @@ namespace Pterodactyl\Exceptions\Service\Pack; -class InvalidFileUploadException extends \Exception +use Pterodactyl\Exceptions\DisplayException; + +class InvalidFileUploadException extends DisplayException { // } diff --git a/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php b/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php new file mode 100644 index 000000000..f13a33581 --- /dev/null +++ b/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php @@ -0,0 +1,32 @@ +. + * + * 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\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class InvalidPackArchiveFormatException extends DisplayException +{ + // +} diff --git a/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php b/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php new file mode 100644 index 000000000..a803d1583 --- /dev/null +++ b/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php @@ -0,0 +1,32 @@ +. + * + * 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\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class UnreadableZipArchiveException extends DisplayException +{ + // +} diff --git a/app/Exceptions/Service/Pack/ZipExtractionException.php b/app/Exceptions/Service/Pack/ZipExtractionException.php new file mode 100644 index 000000000..b465075e3 --- /dev/null +++ b/app/Exceptions/Service/Pack/ZipExtractionException.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\Service\Pack; + +use Pterodactyl\Exceptions\DisplayException; + +class ZipExtractionException extends DisplayException +{ +} diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php index d02273672..9a1b089f3 100644 --- a/app/Http/Controllers/Admin/PackController.php +++ b/app/Http/Controllers/Admin/PackController.php @@ -26,6 +26,12 @@ namespace Pterodactyl\Http\Controllers\Admin; use Log; use Alert; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Services\Packs\PackCreationService; +use Pterodactyl\Services\Packs\PackDeletionService; +use Pterodactyl\Services\Packs\PackUpdateService; +use Pterodactyl\Services\Packs\TemplateUploadService; use Storage; use Illuminate\Http\Request; use Pterodactyl\Models\Pack; @@ -37,10 +43,66 @@ use Pterodactyl\Exceptions\DisplayValidationException; class PackController extends Controller { + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Packs\PackCreationService + */ + protected $creationService; + + /** + * @var \Pterodactyl\Services\Packs\PackDeletionService + */ + protected $deletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Packs\PackUpdateService + */ + protected $packUpdateService; + + /** + * @var \Pterodactyl\Services\Packs\TemplateUploadService + */ + protected $templateUploadService; + + /** + * PackController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Packs\PackCreationService $creationService + * @param \Pterodactyl\Services\Packs\PackDeletionService $deletionService + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Services\Packs\PackUpdateService $packUpdateService + * @param \Pterodactyl\Services\Packs\TemplateUploadService $templateUploadService + */ + public function __construct( + AlertsMessageBag $alert, + PackCreationService $creationService, + PackDeletionService $deletionService, + PackRepositoryInterface $repository, + PackUpdateService $packUpdateService, + TemplateUploadService $templateUploadService + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->repository = $repository; + $this->packUpdateService = $packUpdateService; + $this->templateUploadService = $templateUploadService; + } + /** * Display listing of all packs on the system. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function index(Request $request) @@ -57,7 +119,7 @@ class PackController extends Controller /** * Display new pack creation form. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function create(Request $request) @@ -70,7 +132,7 @@ class PackController extends Controller /** * Display new pack creation modal for use with template upload. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ public function newTemplate(Request $request) @@ -83,42 +145,34 @@ class PackController extends Controller /** * Handle create pack request and route user to location. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException + * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException */ public function store(Request $request) { - $repo = new PackRepository; - - try { - if ($request->input('action') === 'from_template') { - $pack = $repo->createWithTemplate($request->intersect(['option_id', 'file_upload'])); - } else { - $pack = $repo->create($request->intersect([ - 'name', 'description', 'version', 'option_id', - 'selectable', 'visible', 'locked', 'file_upload', - ])); - } - Alert::success('Pack successfully created on the system.')->flash(); - - return redirect()->route('admin.packs.view', $pack->id); - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.packs.new')->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error occured while attempting to add a new service pack. This error has been logged.')->flash(); + if ($request->has('from_template')) { + $pack = $this->templateUploadService->handle($request->input('option_id'), $request->input('file_upload')); + } else { + $pack = $this->creationService->handle($request->normalize(), $request->input('file_upload')); } - return redirect()->route('admin.packs.new')->withInput(); + $this->alert->success(trans('admin/pack.notices.pack_created'))->flash(); + + return redirect()->route('admin.packs.view', $pack->id); } /** * Display pack view template to user. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\View\View */ public function view(Request $request, $id) @@ -132,8 +186,8 @@ class PackController extends Controller /** * Handle updating or deleting pack information. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param int $id * @return \Illuminate\Http\RedirectResponse */ public function update(Request $request, $id) @@ -168,9 +222,9 @@ class PackController extends Controller /** * Creates an archive of the pack and downloads it to the browser. * - * @param \Illuminate\Http\Request $request - * @param int $id - * @param bool $files + * @param \Illuminate\Http\Request $request + * @param int $id + * @param bool $files * @return \Symfony\Component\HttpFoundation\BinaryFileResponse */ public function export(Request $request, $id, $files = false) diff --git a/app/Services/Packs/PackCreationService.php b/app/Services/Packs/PackCreationService.php index ff74b27cb..10daaeb3b 100644 --- a/app/Services/Packs/PackCreationService.php +++ b/app/Services/Packs/PackCreationService.php @@ -24,20 +24,20 @@ namespace Pterodactyl\Services\Packs; +use Illuminate\Http\UploadedFile; use Ramsey\Uuid\Uuid; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Contracts\Repository\PackRepositoryInterface; -use Illuminate\Contracts\Config\Repository as ConfigRepository; use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; class PackCreationService { - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; + const VALID_UPLOAD_TYPES = [ + 'application/gzip', + 'application/x-gzip', + ]; /** * @var \Illuminate\Database\ConnectionInterface @@ -57,18 +57,15 @@ class PackCreationService /** * PackCreationService constructor. * - * @param \Illuminate\Contracts\Config\Repository $config * @param \Illuminate\Database\ConnectionInterface $connection * @param \Illuminate\Contracts\Filesystem\Factory $storage * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository */ public function __construct( - ConfigRepository $config, ConnectionInterface $connection, FilesystemFactory $storage, PackRepositoryInterface $repository ) { - $this->config = $config; $this->connection = $connection; $this->repository = $repository; $this->storage = $storage; @@ -85,15 +82,17 @@ class PackCreationService * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException */ - public function handle(array $data, $file = null) + public function handle(array $data, UploadedFile $file = null) { if (! is_null($file)) { if (! $file->isValid()) { - throw new InvalidFileUploadException; + throw new InvalidFileUploadException(trans('admin/exceptions.packs.invalid_upload')); } - if (! in_array($file->getMimeType(), $this->config->get('pterodactyl.files.pack_types'))) { - throw new InvalidFileMimeTypeException; + if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { + throw new InvalidFileMimeTypeException(trans('admin/exceptions.packs.invalid_mime', [ + 'type' => implode(', ', self::VALID_UPLOAD_TYPES), + ])); } } @@ -107,7 +106,7 @@ class PackCreationService ['uuid' => Uuid::uuid4()], $data )); - $this->storage->disk('default')->makeDirectory('packs/' . $pack->uuid); + $this->storage->disk()->makeDirectory('packs/' . $pack->uuid); if (! is_null($file)) { $file->storeAs('packs/' . $pack->uuid, 'archive.tar.gz'); } diff --git a/app/Services/Packs/PackDeletionService.php b/app/Services/Packs/PackDeletionService.php new file mode 100644 index 000000000..f38a2df71 --- /dev/null +++ b/app/Services/Packs/PackDeletionService.php @@ -0,0 +1,100 @@ +. + * + * 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\Services\Packs; + +use Pterodactyl\Models\Pack; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; + +class PackDeletionService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * @var \Illuminate\Contracts\Filesystem\Factory + */ + protected $storage; + + /** + * PackDeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Contracts\Filesystem\Factory $storage + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + */ + public function __construct( + ConnectionInterface $connection, + FilesystemFactory $storage, + PackRepositoryInterface $repository, + ServerRepositoryInterface $serverRepository + ) { + $this->connection = $connection; + $this->repository = $repository; + $this->serverRepository = $serverRepository; + $this->storage = $storage; + } + + /** + * Delete a pack from the database as well as the archive stored on the server. + * + * @param int|\Pterodactyl\Models\Pack$pack + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($pack) + { + if (! $pack instanceof Pack) { + $pack = $this->repository->withColumns(['id', 'uuid'])->find($pack); + } + + $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack]]); + if ($count !== 0) { + throw new HasActiveServersException(trans('admin/exceptions.packs.delete_has_servers')); + } + + $this->connection->beginTransaction(); + $this->repository->delete($pack->id); + $this->storage->disk()->deleteDirectory('packs/' . $pack->uuid); + $this->connection->commit(); + } +} diff --git a/app/Services/Packs/PackUpdateService.php b/app/Services/Packs/PackUpdateService.php new file mode 100644 index 000000000..22e387270 --- /dev/null +++ b/app/Services/Packs/PackUpdateService.php @@ -0,0 +1,90 @@ +. + * + * 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\Services\Packs; + +use Pterodactyl\Models\Pack; +use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class PackUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * PackUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + */ + public function __construct( + PackRepositoryInterface $repository, + ServerRepositoryInterface $serverRepository + ) { + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Update a pack. + * + * @param int|\Pterodactyl\Models\Pack $pack + * @param array $data + * @return bool + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($pack, array $data) + { + if (! $pack instanceof Pack) { + $pack = $this->repository->withColumns(['id', 'option_id'])->find($pack); + } + + if ((int) array_get($data, 'option_id', $pack->option_id) !== $pack->option_id) { + $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); + + if ($count !== 0) { + throw new HasActiveServersException(trans('admin/exceptions.packs.update_has_servers')); + } + } + + // Transform values to boolean + $data['selectable'] = isset($data['selectable']); + $data['visible'] = isset($data['visible']); + $data['locked'] = isset($data['locked']); + + return $this->repository->withoutFresh()->update($pack->id, $data); + } +} diff --git a/app/Services/Packs/TemplateUploadService.php b/app/Services/Packs/TemplateUploadService.php new file mode 100644 index 000000000..c4e5b5fb9 --- /dev/null +++ b/app/Services/Packs/TemplateUploadService.php @@ -0,0 +1,140 @@ +. + * + * 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\Services\Packs; + +use ZipArchive; +use Illuminate\Http\UploadedFile; +use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; +use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; +use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException; +use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException; + +class TemplateUploadService +{ + const VALID_UPLOAD_TYPES = [ + 'application/zip', + 'text/plain', + 'application/json', + ]; + + /** + * @var \ZipArchive + */ + protected $archive; + + /** + * @var \Pterodactyl\Services\Packs\PackCreationService + */ + protected $creationService; + + /** + * TemplateUploadService constructor. + * + * @param \Pterodactyl\Services\Packs\PackCreationService $creationService + * @param \ZipArchive $archive + */ + public function __construct( + PackCreationService $creationService, + ZipArchive $archive + ) { + $this->archive = $archive; + $this->creationService = $creationService; + } + + /** + * Process an uploaded file to create a new pack from a JSON or ZIP format. + * + * @param int $option + * @param \Illuminate\Http\UploadedFile $file + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException + */ + public function handle($option, UploadedFile $file) + { + if (! $file->isValid()) { + throw new InvalidFileUploadException(trans('admin/exceptions.packs.invalid_upload')); + } + + if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) { + throw new InvalidFileMimeTypeException(trans('admin/exceptions.packs.invalid_mime', [ + 'type' => implode(', ', self::VALID_UPLOAD_TYPES), + ])); + } + + if ($file->getMimeType() === 'application/zip') { + return $this->handleArchive($option, $file); + } else { + $json = json_decode($file->openFile()->fread($file->getSize()), true); + $json['option_id'] = $option; + + return $this->creationService->handle($json); + } + } + + /** + * Process a ZIP file to create a pack and stored archive. + * + * @param int $option + * @param \Illuminate\Http\UploadedFile $file + * @return \Pterodactyl\Models\Pack + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException + * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException + * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException + */ + protected function handleArchive($option, $file) + { + if (! $this->archive->open($file->getRealPath())) { + throw new UnreadableZipArchiveException(trans('admin/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')); + } + + $json = json_decode($this->archive->getFromName('import.json'), true); + $json['option_id'] = $option; + + $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')); + } + + $this->archive->close(); + + return $pack; + } +} diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 6e6d92aa1..6e7767db9 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -156,10 +156,6 @@ return [ 'text/x-perl', 'text/x-shellscript', ], - 'pack_types' => [ - 'application/gzip', - 'application/x-gzip', - ], ], /* diff --git a/resources/lang/en/admin/exceptions.php b/resources/lang/en/admin/exceptions.php index bea2c83f9..4ff0c4b4f 100644 --- a/resources/lang/en/admin/exceptions.php +++ b/resources/lang/en/admin/exceptions.php @@ -45,4 +45,13 @@ return [ 'reserved_name' => 'The environment variable :name is protected and cannot be assigned to a variable.', ], ], + 'packs' => [ + 'delete_has_servers' => 'Cannot delete a pack that is attached to active servers.', + 'update_has_servers' => 'Cannot modify the associated option ID when servers are currently attached to a pack.', + 'invalid_upload' => 'The file provided does not appear to be valid.', + 'invalid_mime' => 'The file provided does not meet the required type :type', + 'unreadable' => 'The archive provided could not be opened by the server.', + 'zip_extraction' => 'An exception was encountered while attempting to extract the archive provided onto the server.', + 'invalid_archive_exception' => 'The pack archive provided appears to be missing a required archive.tar.gz or import.json file in the base directory.', + ], ]; diff --git a/resources/lang/en/admin/pack.php b/resources/lang/en/admin/pack.php new file mode 100644 index 000000000..6e51903a3 --- /dev/null +++ b/resources/lang/en/admin/pack.php @@ -0,0 +1,29 @@ +. + * + * 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. + */ + +return [ + 'notices' => [ + 'pack_created' => 'A new pack was successfully created on the system and is now available for deployment to servers.', + ], +];