From e0d03513e41c307aa156e2ebb4f197d710ed74f3 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 27 Oct 2017 21:42:53 -0500 Subject: [PATCH 01/10] Cleanup frontend controllers and middleware --- .../ScheduleRepositoryInterface.php | 13 +- .../Repository/SubuserRepositoryInterface.php | 25 +-- .../Controllers/Server/ConsoleController.php | 39 ++-- .../Server/Files/DownloadController.php | 30 ++-- .../Server/Files/FileActionsController.php | 60 ++----- .../Server/Files/RemoteRequestController.php | 77 +++----- .../Server/Settings/AllocationController.php | 1 - .../Server/Settings/StartupController.php | 5 +- .../Controllers/Server/SubuserController.php | 91 ++++------ .../Controllers/Server/TaskController.php | 166 ------------------ .../Server/Tasks/TaskManagementController.php | 79 ++++----- app/Http/Kernel.php | 3 +- .../AccessingValidServer.php} | 11 +- .../Server/ScheduleBelongsToServer.php | 33 +--- .../Server/SubuserBelongsToServer.php | 32 ++-- .../Server/ScheduleCreationFormRequest.php | 18 +- .../Requests/Server/ServerFormRequest.php | 29 +++ .../Subuser/SubuserStoreFormRequest.php | 31 ++++ .../Subuser/SubuserUpdateFormRequest.php | 30 ++++ .../Server/UpdateFileContentsFormRequest.php | 56 +++--- app/Models/Subuser.php | 10 ++ .../Eloquent/ScheduleRepository.php | 26 ++- .../Eloquent/SubuserRepository.php | 38 ++-- .../Subusers/SubuserDeletionService.php | 8 +- .../Subusers/SubuserUpdateService.php | 41 ++--- public/js/laroute.js | 2 +- .../js/frontend/files/filemanager.min.js | 2 +- .../js/frontend/files/filemanager.min.js.map | 2 +- .../js/frontend/files/src/index.js | 2 +- .../js/frontend/tasks/management-actions.js | 2 +- .../pterodactyl/server/users/index.blade.php | 8 +- .../pterodactyl/server/users/view.blade.php | 2 +- routes/server.php | 22 +-- 33 files changed, 400 insertions(+), 594 deletions(-) delete mode 100644 app/Http/Controllers/Server/TaskController.php rename app/Http/Middleware/{ServerAuthenticate.php => Server/AccessingValidServer.php} (92%) create mode 100644 app/Http/Requests/Server/ServerFormRequest.php create mode 100644 app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php create mode 100644 app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php diff --git a/app/Contracts/Repository/ScheduleRepositoryInterface.php b/app/Contracts/Repository/ScheduleRepositoryInterface.php index 3375a6b24..7e28b016f 100644 --- a/app/Contracts/Repository/ScheduleRepositoryInterface.php +++ b/app/Contracts/Repository/ScheduleRepositoryInterface.php @@ -9,23 +9,28 @@ namespace Pterodactyl\Contracts\Repository; +use Pterodactyl\Models\Schedule; +use Illuminate\Support\Collection; + interface ScheduleRepositoryInterface extends RepositoryInterface { /** * Return all of the schedules for a given server. * * @param int $server - * @return \Illuminate\Database\Eloquent\Collection + * @return \Illuminate\Support\Collection */ - public function getServerSchedules($server); + public function findServerSchedules(int $server): Collection; /** * Return a schedule model with all of the associated tasks as a relationship. * * @param int $schedule - * @return \Illuminate\Support\Collection + * @return \Pterodactyl\Models\Schedule + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getScheduleWithTasks($schedule); + public function getScheduleWithTasks(int $schedule): Schedule; /** * Return all of the schedules that should be processed. diff --git a/app/Contracts/Repository/SubuserRepositoryInterface.php b/app/Contracts/Repository/SubuserRepositoryInterface.php index ab1192475..a740bad9c 100644 --- a/app/Contracts/Repository/SubuserRepositoryInterface.php +++ b/app/Contracts/Repository/SubuserRepositoryInterface.php @@ -1,35 +1,28 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Contracts\Repository; +use Pterodactyl\Models\Subuser; + interface SubuserRepositoryInterface extends RepositoryInterface { /** * Return a subuser with the associated server relationship. * - * @param int $id + * @param \Pterodactyl\Models\Subuser $subuser + * @param bool $refresh * @return \Pterodactyl\Models\Subuser - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getWithServer($id); + public function getWithServer(Subuser $subuser, bool $refresh = false): Subuser; /** * Return a subuser with the associated permissions relationship. * - * @param int $id - * @return \Illuminate\Database\Eloquent\Collection - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @param \Pterodactyl\Models\Subuser $subuser + * @param bool $refresh + * @return \Pterodactyl\Models\Subuser */ - public function getWithPermissions($id); + public function getWithPermissions(Subuser $subuser, bool $refresh = false): Subuser; /** * Return a subuser and associated permissions given a user_id and server_id. diff --git a/app/Http/Controllers/Server/ConsoleController.php b/app/Http/Controllers/Server/ConsoleController.php index f8be5044a..1e873474c 100644 --- a/app/Http/Controllers/Server/ConsoleController.php +++ b/app/Http/Controllers/Server/ConsoleController.php @@ -1,15 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Server; -use Illuminate\Contracts\Session\Session; +use Illuminate\View\View; +use Illuminate\Http\Request; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Traits\Controllers\JavascriptInjection; use Illuminate\Contracts\Config\Repository as ConfigRepository; @@ -23,35 +17,27 @@ class ConsoleController extends Controller */ protected $config; - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - /** * ConsoleController constructor. * * @param \Illuminate\Contracts\Config\Repository $config - * @param \Illuminate\Contracts\Session\Session $session */ - public function __construct( - ConfigRepository $config, - Session $session - ) { + public function __construct(ConfigRepository $config) + { $this->config = $config; - $this->session = $session; } /** * Render server index page with the console and power options. * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View */ - public function index() + public function index(Request $request): View { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); - $this->injectJavascript([ + $this->setRequest($request)->injectJavascript([ 'meta' => [ 'saveFile' => route('server.files.save', $server->uuidShort), 'csrfToken' => csrf_token(), @@ -68,11 +54,12 @@ class ConsoleController extends Controller /** * Render a stand-alone console in the browser. * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View */ - public function console() + public function console(Request $request): View { - $this->injectJavascript(['config' => [ + $this->setRequest($request)->injectJavascript(['config' => [ 'console_count' => $this->config->get('pterodactyl.console.count'), 'console_freq' => $this->config->get('pterodactyl.console.frequency'), ]]); diff --git a/app/Http/Controllers/Server/Files/DownloadController.php b/app/Http/Controllers/Server/Files/DownloadController.php index 936d96357..79155a63a 100644 --- a/app/Http/Controllers/Server/Files/DownloadController.php +++ b/app/Http/Controllers/Server/Files/DownloadController.php @@ -9,8 +9,9 @@ namespace Pterodactyl\Http\Controllers\Server\Files; +use Illuminate\Http\Request; use Illuminate\Cache\Repository; -use Illuminate\Contracts\Session\Session; +use Illuminate\Http\RedirectResponse; use Pterodactyl\Http\Controllers\Controller; class DownloadController extends Controller @@ -20,42 +21,35 @@ class DownloadController extends Controller */ protected $cache; - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - /** * DownloadController constructor. * - * @param \Illuminate\Cache\Repository $cache - * @param \Illuminate\Contracts\Session\Session $session + * @param \Illuminate\Cache\Repository $cache */ - public function __construct(Repository $cache, Session $session) + public function __construct(Repository $cache) { $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 + * @param \Illuminate\Http\Request $request + * @param string $uuid + * @param string $file + * @return \Illuminate\Http\RedirectResponse * * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index($uuid, $file) + public function index(Request $request, string $uuid, string $file): RedirectResponse { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('download-files', $server); $token = str_random(40); + $node = $server->getRelation('node'); $this->cache->tags(['Server:Downloads'])->put($token, ['server' => $server->uuid, 'path' => $file], 5); - return redirect(sprintf( - '%s://%s:%s/v1/server/file/download/%s', $server->node->scheme, $server->node->fqdn, $server->node->daemonListen, $token - )); + return redirect(sprintf('%s://%s:%s/v1/server/file/download/%s', $node->scheme, $node->fqdn, $node->daemonListen, $token)); } } diff --git a/app/Http/Controllers/Server/Files/FileActionsController.php b/app/Http/Controllers/Server/Files/FileActionsController.php index 301f980cf..9c5b77ea1 100644 --- a/app/Http/Controllers/Server/Files/FileActionsController.php +++ b/app/Http/Controllers/Server/Files/FileActionsController.php @@ -9,15 +9,14 @@ namespace Pterodactyl\Http\Controllers\Server\Files; -use Illuminate\Log\Writer; +use Illuminate\View\View; use Illuminate\Http\Request; -use Illuminate\Contracts\Session\Session; use GuzzleHttp\Exception\RequestException; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Traits\Controllers\JavascriptInjection; use Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest; use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class FileActionsController extends Controller { @@ -26,30 +25,16 @@ class FileActionsController extends Controller /** * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface */ - protected $fileRepository; - - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - - /** - * @var \Illuminate\Log\Writer - */ - protected $writer; + protected $repository; /** * FileActionsController constructor. * - * @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $fileRepository - * @param \Illuminate\Contracts\Session\Session $session - * @param \Illuminate\Log\Writer $writer + * @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $repository */ - public function __construct(FileRepositoryInterface $fileRepository, Session $session, Writer $writer) + public function __construct(FileRepositoryInterface $repository) { - $this->fileRepository = $fileRepository; - $this->session = $session; - $this->writer = $writer; + $this->repository = $repository; } /** @@ -60,12 +45,12 @@ class FileActionsController extends Controller * * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index(Request $request) + public function index(Request $request): View { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('list-files', $server); - $this->injectJavascript([ + $this->setRequest($request)->injectJavascript([ 'meta' => [ 'directoryList' => route('server.files.directory-list', $server->uuidShort), 'csrftoken' => csrf_token(), @@ -92,10 +77,10 @@ class FileActionsController extends Controller * * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create(Request $request) + public function create(Request $request): View { - $this->authorize('create-files', $this->session->get('server_data.model')); - $this->injectJavascript(); + $this->authorize('create-files', $request->attributes->get('server')); + $this->setRequest($request)->injectJavascript(); return view('server.files.add', [ 'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/', @@ -114,31 +99,24 @@ class FileActionsController extends Controller * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateFileContentsFormRequest $request, $uuid, $file) + public function update(UpdateFileContentsFormRequest $request, string $uuid, string $file): View { - $server = $this->session->get('server_data.model'); - $this->authorize('edit-files', $server); + $server = $request->attributes->get('server'); $dirname = pathinfo($file, PATHINFO_DIRNAME); try { - $content = $this->fileRepository->setNode($server->node_id) - ->setAccessServer($server->uuid) - ->setAccessToken($this->session->get('server_data.token')) + $content = $this->repository->setNode($server->node_id)->setAccessServer($server->uuid) + ->setAccessToken($request->attributes->get('server_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(), - ])); + throw new DaemonConnectionException($exception); } - $this->injectJavascript(['stat' => $request->getStats()]); + $this->setRequest($request)->injectJavascript(['stat' => $request->attributes->get('file_stats')]); return view('server.files.edit', [ 'file' => $file, - 'stat' => $request->getStats(), + 'stat' => $request->attributes->get('file_stats'), '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 index d355ee759..5deba5f95 100644 --- a/app/Http/Controllers/Server/Files/RemoteRequestController.php +++ b/app/Http/Controllers/Server/Files/RemoteRequestController.php @@ -1,21 +1,15 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Server\Files; -use Illuminate\Log\Writer; +use Illuminate\View\View; use Illuminate\Http\Request; -use Illuminate\Contracts\Session\Session; +use Illuminate\Http\Response; use GuzzleHttp\Exception\RequestException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class RemoteRequestController extends Controller { @@ -27,50 +21,33 @@ class RemoteRequestController extends Controller /** * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface */ - protected $fileRepository; - - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - - /** - * @var \Illuminate\Log\Writer - */ - protected $writer; + protected $repository; /** * 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 + * @param \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface $repository */ - public function __construct( - ConfigRepository $config, - FileRepositoryInterface $fileRepository, - Session $session, - Writer $writer - ) { + public function __construct(ConfigRepository $config, FileRepositoryInterface $repository) + { $this->config = $config; - $this->fileRepository = $fileRepository; - $this->session = $session; - $this->writer = $writer; + $this->repository = $repository; } /** * Return a listing of a servers file directory. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse|\Illuminate\View\View + * @return \Illuminate\View\View * * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function directory(Request $request) + public function directory(Request $request): View { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('list-files', $server); $requestDirectory = '/' . trim(urldecode($request->input('directory', '/')), '/'); @@ -89,17 +66,12 @@ class RemoteRequestController extends Controller } try { - $listing = $this->fileRepository->setNode($server->node_id) + $listing = $this->repository->setNode($server->node_id) ->setAccessServer($server->uuid) - ->setAccessToken($this->session->get('server_data.token')) + ->setAccessToken($request->attributes->get('server_token')) ->getDirectory($requestDirectory); } catch (RequestException $exception) { - $this->writer->warning($exception); - $response = $exception->getResponse(); - - return response()->json(['error' => trans('exceptions.daemon_connection_failed', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ])], 500); + throw new DaemonConnectionException($exception); } return view('server.files.list', [ @@ -114,31 +86,26 @@ class RemoteRequestController extends Controller * Put the contents of a file onto the daemon. * * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\Response * * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function store(Request $request, $uuid) + public function store(Request $request): Response { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('save-files', $server); try { - $this->fileRepository->setNode($server->node_id) + $this->repository->setNode($server->node_id) ->setAccessServer($server->uuid) - ->setAccessToken($this->session->get('server_data.token')) + ->setAccessToken($request->attributes->get('server_token')) ->putContent($request->input('file'), $request->input('contents')); return response('', 204); } catch (RequestException $exception) { - $this->writer->warning($exception); - $response = $exception->getResponse(); - - return response()->json(['error' => trans('exceptions.daemon_connection_failed', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ])], 500); + throw new DaemonConnectionException($exception); } } } diff --git a/app/Http/Controllers/Server/Settings/AllocationController.php b/app/Http/Controllers/Server/Settings/AllocationController.php index 18a42f963..21baf7c0d 100644 --- a/app/Http/Controllers/Server/Settings/AllocationController.php +++ b/app/Http/Controllers/Server/Settings/AllocationController.php @@ -4,7 +4,6 @@ namespace Pterodactyl\Http\Controllers\Server\Settings; use Illuminate\View\View; use Illuminate\Http\Request; -use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Contracts\Extensions\HashidsInterface; diff --git a/app/Http/Controllers/Server/Settings/StartupController.php b/app/Http/Controllers/Server/Settings/StartupController.php index 5d299c42e..789926922 100644 --- a/app/Http/Controllers/Server/Settings/StartupController.php +++ b/app/Http/Controllers/Server/Settings/StartupController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Server\Settings; +use Illuminate\View\View; use Illuminate\Http\Request; use Pterodactyl\Models\User; use Illuminate\Http\RedirectResponse; @@ -57,11 +58,11 @@ class StartupController extends Controller * @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index(Request $request) + public function index(Request $request): View { $server = $request->attributes->get('server'); $this->authorize('view-startup', $server); - $this->injectJavascript(); + $this->setRequest($request)->injectJavascript(); $data = $this->commandViewService->handle($server->id); diff --git a/app/Http/Controllers/Server/SubuserController.php b/app/Http/Controllers/Server/SubuserController.php index 262c3df23..eb09fb63c 100644 --- a/app/Http/Controllers/Server/SubuserController.php +++ b/app/Http/Controllers/Server/SubuserController.php @@ -1,24 +1,21 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Server; +use Illuminate\View\View; use Illuminate\Http\Request; +use Illuminate\Http\Response; use Pterodactyl\Models\Permission; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; -use Illuminate\Contracts\Session\Session; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Subusers\SubuserUpdateService; use Pterodactyl\Traits\Controllers\JavascriptInjection; use Pterodactyl\Services\Subusers\SubuserCreationService; use Pterodactyl\Services\Subusers\SubuserDeletionService; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest; +use Pterodactyl\Http\Requests\Server\Subuser\SubuserUpdateFormRequest; class SubuserController extends Controller { @@ -34,11 +31,6 @@ class SubuserController extends Controller */ protected $repository; - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - /** * @var \Pterodactyl\Services\Subusers\SubuserCreationService */ @@ -58,7 +50,6 @@ class SubuserController extends Controller * SubuserController constructor. * * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Illuminate\Contracts\Session\Session $session * @param \Pterodactyl\Services\Subusers\SubuserCreationService $subuserCreationService * @param \Pterodactyl\Services\Subusers\SubuserDeletionService $subuserDeletionService * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository @@ -66,7 +57,6 @@ class SubuserController extends Controller */ public function __construct( AlertsMessageBag $alert, - Session $session, SubuserCreationService $subuserCreationService, SubuserDeletionService $subuserDeletionService, SubuserRepositoryInterface $repository, @@ -74,7 +64,6 @@ class SubuserController extends Controller ) { $this->alert = $alert; $this->repository = $repository; - $this->session = $session; $this->subuserCreationService = $subuserCreationService; $this->subuserDeletionService = $subuserDeletionService; $this->subuserUpdateService = $subuserUpdateService; @@ -83,17 +72,16 @@ class SubuserController extends Controller /** * Displays the subuser overview index. * + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View * * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function index() + public function index(Request $request): View { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('list-subusers', $server); - - $this->injectJavascript(); + $this->setRequest($request)->injectJavascript(); return view('server.users.index', [ 'subusers' => $this->repository->findWhere([['server_id', '=', $server->id]]), @@ -101,27 +89,25 @@ class SubuserController extends Controller } /** - * Displays the a single subuser overview. + * Displays a single subuser overview. * - * @param string $uuid - * @param int $id + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View * * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view($uuid, $id) + public function view(Request $request): View { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('view-subuser', $server); - $subuser = $this->repository->getWithPermissions($id); - $this->injectJavascript(); + $subuser = $this->repository->getWithPermissions($request->attributes->get('subuser')); + $this->setRequest($request)->injectJavascript(); return view('server.users.view', [ 'subuser' => $subuser, 'permlist' => Permission::getPermissions(), - 'permissions' => $subuser->permissions->mapWithKeys(function ($item, $key) { + 'permissions' => $subuser->getRelation('permissions')->mapWithKeys(function ($item) { return [$item->permission => true]; }), ]); @@ -130,9 +116,9 @@ class SubuserController extends Controller /** * Handles editing a subuser. * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id + * @param \Pterodactyl\Http\Requests\Server\Subuser\SubuserUpdateFormRequest $request + * @param string $uuid + * @param string $hash * @return \Illuminate\Http\RedirectResponse * * @throws \Illuminate\Auth\Access\AuthorizationException @@ -140,30 +126,26 @@ class SubuserController extends Controller * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(Request $request, $uuid, $id) + public function update(SubuserUpdateFormRequest $request, string $uuid, string $hash): RedirectResponse { - $server = $this->session->get('server_data.model'); - $this->authorize('edit-subuser', $server); - - $this->subuserUpdateService->handle($id, $request->input('permissions', [])); + $this->subuserUpdateService->handle($request->attributes->get('subuser'), $request->input('permissions')); $this->alert->success(trans('server.users.user_updated'))->flash(); - return redirect()->route('server.subusers.view', ['uuid' => $uuid, 'id' => $id]); + return redirect()->route('server.subusers.view', ['uuid' => $uuid, 'subuser' => $hash]); } /** * Display new subuser creation page. * + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View - * * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create(Request $request): View { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('create-subuser', $server); - - $this->injectJavascript(); + $this->setRequest($request)->injectJavascript(); return view('server.users.new', ['permissions' => Permission::getPermissions()]); } @@ -171,24 +153,22 @@ class SubuserController extends Controller /** * Handles creating a new subuser. * - * @param \Illuminate\Http\Request $request - * @param string $uuid + * @param \Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest $request + * @param string $uuid * @return \Illuminate\Http\RedirectResponse * * @throws \Exception * @throws \Illuminate\Auth\Access\AuthorizationException - * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException */ - public function store(Request $request, $uuid) + public function store(SubuserStoreFormRequest $request, $uuid): RedirectResponse { - $server = $this->session->get('server_data.model'); - $this->authorize('create-subuser', $server); + $server = $request->attributes->get('server'); - $subuser = $this->subuserCreationService->handle($server, $request->input('email'), $request->input('permissions', [])); + $subuser = $this->subuserCreationService->handle($server, $request->input('email'), $request->input('permissions')); $this->alert->success(trans('server.users.user_assigned'))->flash(); return redirect()->route('server.subusers.view', [ @@ -200,20 +180,19 @@ class SubuserController extends Controller /** * Handles deleting a subuser. * - * @param string $uuid - * @param int $id + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response * * @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function delete($uuid, $id) + public function delete(Request $request): Response { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('delete-subuser', $server); - $this->subuserDeletionService->handle($id); + $this->subuserDeletionService->handle($request->attributes->get('subuser')); return response('', 204); } diff --git a/app/Http/Controllers/Server/TaskController.php b/app/Http/Controllers/Server/TaskController.php deleted file mode 100644 index be9f8eea5..000000000 --- a/app/Http/Controllers/Server/TaskController.php +++ /dev/null @@ -1,166 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Server; - -use Log; -use Alert; -use Illuminate\Http\Request; -use Pterodactyl\Models\Server; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\TaskRepository; -use Pterodactyl\Exceptions\DisplayValidationException; - -class TaskController extends Controller -{ - /** - * Display task index page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function index(Request $request, $uuid) - { - $server = Server::byUuid($uuid)->load('tasks'); - $this->authorize('list-tasks', $server); - $server->js(); - - return view('server.schedules.index', [ - 'server' => $server, - 'node' => $server->node, - 'tasks' => $server->tasks, - 'actions' => [ - 'command' => trans('server.schedules.actions.command'), - 'power' => trans('server.schedules.actions.power'), - ], - ]); - } - - /** - * Display new task page. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function create(Request $request, $uuid) - { - $server = Server::byUuid($uuid); - $this->authorize('create-task', $server); - $server->js(); - - return view('server.schedules.new', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Handle creation of new task. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\RedirectResponse - */ - public function store(Request $request, $uuid) - { - $server = Server::byUuid($uuid); - $this->authorize('create-task', $server); - - $repo = new TaskRepository; - try { - $repo->create($server->id, $request->user()->id, $request->except([ - '_token', - ])); - - return redirect()->route('server.schedules', $uuid); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.schedules.new', $uuid)->withErrors(json_decode($ex->getMessage()))->withInput(); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unknown error occured while attempting to create this task.')->flash(); - } - - return redirect()->route('server.schedules.new', $uuid); - } - - /** - * Handle deletion of a task. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id - * @return \Illuminate\Http\JsonResponse - */ - public function delete(Request $request, $uuid, $id) - { - $server = Server::byUuid($uuid)->load('tasks'); - $this->authorize('delete-task', $server); - - $task = $server->tasks->where('id', $id)->first(); - if (! $task) { - return response()->json([ - 'error' => 'No task by that ID was found associated with this server.', - ], 404); - } - - $repo = new TaskRepository; - try { - $repo->delete($id); - - return response()->json([], 204); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'A server error occured while attempting to delete this task.', - ], 503); - } - } - - /** - * Toggle the status of a task. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @param int $id - * @return \Illuminate\Http\JsonResponse - */ - public function toggle(Request $request, $uuid, $id) - { - $server = Server::byUuid($uuid)->load('tasks'); - $this->authorize('toggle-task', $server); - - $task = $server->tasks->where('id', $id)->first(); - if (! $task) { - return response()->json([ - 'error' => 'No task by that ID was found associated with this server.', - ], 404); - } - - $repo = new TaskRepository; - try { - $resp = $repo->toggle($id); - - return response()->json([ - 'status' => $resp, - ]); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'A server error occured while attempting to toggle this task.', - ], 503); - } - } -} diff --git a/app/Http/Controllers/Server/Tasks/TaskManagementController.php b/app/Http/Controllers/Server/Tasks/TaskManagementController.php index f493bfaca..e61a560fa 100644 --- a/app/Http/Controllers/Server/Tasks/TaskManagementController.php +++ b/app/Http/Controllers/Server/Tasks/TaskManagementController.php @@ -1,17 +1,12 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Server\Tasks; +use Illuminate\View\View; use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; -use Illuminate\Contracts\Session\Session; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Traits\Controllers\JavascriptInjection; @@ -43,24 +38,17 @@ class TaskManagementController extends Controller */ protected $repository; - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - /** * TaskManagementController constructor. * * @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids - * @param \Illuminate\Contracts\Session\Session $session * @param \Pterodactyl\Services\Schedules\ScheduleCreationService $creationService * @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $repository */ public function __construct( AlertsMessageBag $alert, HashidsInterface $hashids, - Session $session, ScheduleCreationService $creationService, ScheduleRepositoryInterface $repository ) { @@ -68,24 +56,24 @@ class TaskManagementController extends Controller $this->creationService = $creationService; $this->hashids = $hashids; $this->repository = $repository; - $this->session = $session; } /** * Display the task page listing. * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View * * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index(Request $request): View { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('list-schedules', $server); - $this->injectJavascript(); + $this->setRequest($request)->injectJavascript(); return view('server.schedules.index', [ - 'schedules' => $this->repository->getServerSchedules($server->id), + 'schedules' => $this->repository->findServerSchedules($server->id), 'actions' => [ 'command' => trans('server.schedule.actions.command'), 'power' => trans('server.schedule.actions.power'), @@ -96,38 +84,39 @@ class TaskManagementController extends Controller /** * Display the task creation page. * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View * * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create(Request $request): View { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $this->authorize('create-schedule', $server); - $this->injectJavascript(); + $this->setRequest($request)->injectJavascript(); return view('server.schedules.new'); } /** + * Handle request to store a new schedule and tasks in the database. + * * @param \Pterodactyl\Http\Requests\Server\ScheduleCreationFormRequest $request * @return \Illuminate\Http\RedirectResponse * - * @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException */ - public function store(ScheduleCreationFormRequest $request) + public function store(ScheduleCreationFormRequest $request): RedirectResponse { - $server = $this->session->get('server_data.model'); - $this->authorize('create-schedule', $server); + $server = $request->attributes->get('server'); $schedule = $this->creationService->handle($server, $request->normalize(), $request->getTasks()); $this->alert->success(trans('server.schedules.task_created'))->flash(); return redirect()->route('server.schedules.view', [ 'server' => $server->uuidShort, - 'task' => $schedule->hashid, + 'schedule' => $schedule->hashid, ]); } @@ -136,17 +125,19 @@ class TaskManagementController extends Controller * * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View + * * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function view(Request $request) + public function view(Request $request): View { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $schedule = $request->attributes->get('schedule'); $this->authorize('view-schedule', $server); - $this->injectJavascript([ - 'tasks' => $schedule->tasks->map(function ($schedule) { - return collect($schedule->toArray())->only('action', 'time_offset', 'payload')->all(); + $this->setRequest($request)->injectJavascript([ + 'tasks' => $schedule->getRelation('tasks')->map(function ($task) { + /* @var \Pterodactyl\Models\Task $task */ + return collect($task->toArray())->only('action', 'time_offset', 'payload')->all(); }), ]); @@ -161,18 +152,18 @@ class TaskManagementController extends Controller * * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(ScheduleCreationFormRequest $request) + public function update(ScheduleCreationFormRequest $request): RedirectResponse { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $schedule = $request->attributes->get('schedule'); - $this->authorize('edit-schedule', $server); - // $this->updateService->handle($task, $request->normalize(), $request->getChainedTasks()); - // $this->alert->success(trans('server.schedules.task_updated'))->flash(); + $this->alert->warning('Function is not implemented.')->flash(); + // $this->updateService->handle($task, $request->normalize(), $request->getChainedTasks()); + // $this->alert->success(trans('server.schedules.task_updated'))->flash(); return redirect()->route('server.schedules.view', [ 'server' => $server->uuidShort, - 'task' => $schedule->hashid, + 'schedule' => $schedule->hashid, ]); } @@ -180,13 +171,13 @@ class TaskManagementController extends Controller * Delete a parent task from the Panel. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\Response * * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function delete(Request $request) + public function delete(Request $request): Response { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server_data.model'); $schedule = $request->attributes->get('schedule'); $this->authorize('delete-schedule', $server); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 22e90a903..e5bcb5d61 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http; use Pterodactyl\Http\Middleware\DaemonAuthenticate; use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Routing\Middleware\SubstituteBindings; +use Pterodactyl\Http\Middleware\AccessingValidServer; use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer; use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; @@ -64,7 +65,7 @@ class Kernel extends HttpKernel 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class, - 'server' => \Pterodactyl\Http\Middleware\ServerAuthenticate::class, + 'server' => AccessingValidServer::class, 'subuser.auth' => \Pterodactyl\Http\Middleware\SubuserAccessAuthenticate::class, 'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class, 'daemon-old' => DaemonAuthenticate::class, diff --git a/app/Http/Middleware/ServerAuthenticate.php b/app/Http/Middleware/Server/AccessingValidServer.php similarity index 92% rename from app/Http/Middleware/ServerAuthenticate.php rename to app/Http/Middleware/Server/AccessingValidServer.php index 538382f23..762f9a298 100644 --- a/app/Http/Middleware/ServerAuthenticate.php +++ b/app/Http/Middleware/Server/AccessingValidServer.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Middleware; @@ -19,7 +12,7 @@ use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -class ServerAuthenticate +class AccessingValidServer { /** * @var \Illuminate\Contracts\Config\Repository @@ -42,7 +35,7 @@ class ServerAuthenticate protected $session; /** - * ServerAuthenticate constructor. + * AccessingValidServer constructor. * * @param \Illuminate\Contracts\Config\Repository $config * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository diff --git a/app/Http/Middleware/Server/ScheduleBelongsToServer.php b/app/Http/Middleware/Server/ScheduleBelongsToServer.php index 145429f8c..32205b6ba 100644 --- a/app/Http/Middleware/Server/ScheduleBelongsToServer.php +++ b/app/Http/Middleware/Server/ScheduleBelongsToServer.php @@ -1,51 +1,34 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Middleware\Server; use Closure; -use Illuminate\Contracts\Session\Session; use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class ScheduleBelongsToServer { /** * @var \Pterodactyl\Contracts\Extensions\HashidsInterface */ - protected $hashids; + private $hashids; /** * @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface */ - protected $repository; - - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; + private $repository; /** * TaskAccess constructor. * * @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids - * @param \Illuminate\Contracts\Session\Session $session * @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $repository */ - public function __construct( - HashidsInterface $hashids, - Session $session, - ScheduleRepositoryInterface $repository - ) { + public function __construct(HashidsInterface $hashids, ScheduleRepositoryInterface $repository) + { $this->hashids = $hashids; $this->repository = $repository; - $this->session = $session; } /** @@ -55,18 +38,18 @@ class ScheduleBelongsToServer * @param \Closure $next * @return mixed * - * @throws \Symfony\Component\HttpKernel\Exception\HttpException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ public function handle($request, Closure $next) { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); $scheduleId = $this->hashids->decodeFirst($request->route()->parameter('schedule'), 0); $schedule = $this->repository->getScheduleWithTasks($scheduleId); if (object_get($schedule, 'server_id') !== $server->id) { - abort(404); + throw new NotFoundHttpException; } $request->attributes->set('schedule', $schedule); diff --git a/app/Http/Middleware/Server/SubuserBelongsToServer.php b/app/Http/Middleware/Server/SubuserBelongsToServer.php index b18620f51..100144c16 100644 --- a/app/Http/Middleware/Server/SubuserBelongsToServer.php +++ b/app/Http/Middleware/Server/SubuserBelongsToServer.php @@ -1,42 +1,35 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Middleware\Server; use Closure; -use Illuminate\Contracts\Session\Session; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class SubuserBelongsToServer { /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + * @var \Pterodactyl\Contracts\Extensions\HashidsInterface */ - protected $repository; + private $hashids; /** - * @var \Illuminate\Contracts\Session\Session + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface */ - protected $session; + private $repository; /** * SubuserAccess constructor. * - * @param \Illuminate\Contracts\Session\Session $session + * @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository */ - public function __construct(Session $session, SubuserRepositoryInterface $repository) + public function __construct(HashidsInterface $hashids, SubuserRepositoryInterface $repository) { + $this->hashids = $hashids; $this->repository = $repository; - $this->session = $session; } /** @@ -52,10 +45,11 @@ class SubuserBelongsToServer */ public function handle($request, Closure $next) { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); - $subuser = $this->repository->find($request->route()->parameter('subuser', 0)); - if ($subuser->server_id !== $server->id) { + $hash = $request->route()->parameter('subuser', 0); + $subuser = $this->repository->find($this->hashids->decodeFirst($hash, 0)); + if (! $subuser || $subuser->server_id !== $server->id) { throw new NotFoundHttpException; } @@ -65,6 +59,8 @@ class SubuserBelongsToServer } } + $request->attributes->set('subuser', $subuser); + return $next($request); } } diff --git a/app/Http/Requests/Server/ScheduleCreationFormRequest.php b/app/Http/Requests/Server/ScheduleCreationFormRequest.php index d9fcf8ebf..b892605b7 100644 --- a/app/Http/Requests/Server/ScheduleCreationFormRequest.php +++ b/app/Http/Requests/Server/ScheduleCreationFormRequest.php @@ -9,10 +9,22 @@ namespace Pterodactyl\Http\Requests\Server; -use Pterodactyl\Http\Requests\FrontendUserFormRequest; - -class ScheduleCreationFormRequest extends FrontendUserFormRequest +class ScheduleCreationFormRequest extends ServerFormRequest { + /** + * Permission to validate this request aganist. + * + * @return string + */ + protected function permission(): string + { + if ($this->method() === 'PATCH') { + return 'edit-schedule'; + } + + return 'create-schedule'; + } + /** * Validation rules to apply to the request. * diff --git a/app/Http/Requests/Server/ServerFormRequest.php b/app/Http/Requests/Server/ServerFormRequest.php new file mode 100644 index 000000000..b796a21e0 --- /dev/null +++ b/app/Http/Requests/Server/ServerFormRequest.php @@ -0,0 +1,29 @@ +user()->can($this->permission(), $this->attributes->get('server')); + } +} diff --git a/app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php b/app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php new file mode 100644 index 000000000..861732e81 --- /dev/null +++ b/app/Http/Requests/Server/Subuser/SubuserStoreFormRequest.php @@ -0,0 +1,31 @@ + 'required|email', + 'permissions' => 'present|array', + ]; + } +} diff --git a/app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php b/app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php new file mode 100644 index 000000000..284b87553 --- /dev/null +++ b/app/Http/Requests/Server/Subuser/SubuserUpdateFormRequest.php @@ -0,0 +1,30 @@ + 'present|array', + ]; + } +} diff --git a/app/Http/Requests/Server/UpdateFileContentsFormRequest.php b/app/Http/Requests/Server/UpdateFileContentsFormRequest.php index 46b6e4add..051e78dbd 100644 --- a/app/Http/Requests/Server/UpdateFileContentsFormRequest.php +++ b/app/Http/Requests/Server/UpdateFileContentsFormRequest.php @@ -9,22 +9,24 @@ namespace Pterodactyl\Http\Requests\Server; -use Illuminate\Log\Writer; -use Illuminate\Contracts\Session\Session; use GuzzleHttp\Exception\RequestException; use Illuminate\Contracts\Config\Repository; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Requests\FrontendUserFormRequest; use Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException; use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; use Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -class UpdateFileContentsFormRequest extends FrontendUserFormRequest +class UpdateFileContentsFormRequest extends ServerFormRequest { /** - * @var object + * Return the permission string to validate this request aganist. + * + * @return string */ - protected $stats; + protected function permission(): string + { + return 'edit-files'; + } /** * Authorize a request to edit a file. @@ -38,17 +40,13 @@ class UpdateFileContentsFormRequest extends FrontendUserFormRequest */ 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) { + if (! parent::authorize()) { return false; } + $server = $this->attributes->get('server'); + $token = $this->attributes->get('server_token'); + return $this->checkFileCanBeEdited($server, $token); } @@ -61,16 +59,8 @@ class UpdateFileContentsFormRequest extends FrontendUserFormRequest } /** - * Return the file stats from the Daemon. + * Checks if a given file can be edited by a user on this server. * - * @return object - */ - public function getStats() - { - return $this->stats; - } - - /** * @param \Pterodactyl\Models\Server $server * @param string $token * @return bool @@ -80,33 +70,29 @@ class UpdateFileContentsFormRequest extends FrontendUserFormRequest * @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - protected function checkFileCanBeEdited($server, $token) + private 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) + $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(), - ])); + throw new DaemonConnectionException($exception); } - if (! $this->stats->file || ! in_array($this->stats->mime, $config->get('pterodactyl.files.editable'))) { + if (! $stats->file || ! in_array($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')) { + if ($stats->size > $config->get('pterodactyl.files.max_edit_size')) { throw new FileSizeTooLargeException(trans('server.files.exceptions.max_size')); } + $this->attributes->set('file_stats', $stats); + return true; } } diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index ae15182be..bcf5837fb 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -60,6 +60,16 @@ class Subuser extends Model implements CleansAttributes, ValidableContract 'server_id' => 'numeric|exists:servers,id', ]; + /** + * Return a hashid encoded string to represent the ID of the subuser. + * + * @return string + */ + public function getHashidAttribute() + { + return app()->make('hashids')->encode($this->id); + } + /** * Gets the server associated with a subuser. * diff --git a/app/Repositories/Eloquent/ScheduleRepository.php b/app/Repositories/Eloquent/ScheduleRepository.php index 6c19a439d..4c0f5b0f0 100644 --- a/app/Repositories/Eloquent/ScheduleRepository.php +++ b/app/Repositories/Eloquent/ScheduleRepository.php @@ -10,6 +10,8 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Schedule; +use Illuminate\Support\Collection; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; class ScheduleRepository extends EloquentRepository implements ScheduleRepositoryInterface @@ -23,19 +25,33 @@ class ScheduleRepository extends EloquentRepository implements ScheduleRepositor } /** - * {@inheritdoc} + * Return all of the schedules for a given server. + * + * @param int $server + * @return \Illuminate\Support\Collection */ - public function getServerSchedules($server) + public function findServerSchedules(int $server): Collection { return $this->getBuilder()->withCount('tasks')->where('server_id', '=', $server)->get($this->getColumns()); } /** - * {@inheritdoc} + * Return a schedule model with all of the associated tasks as a relationship. + * + * @param int $schedule + * @return \Pterodactyl\Models\Schedule + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getScheduleWithTasks($schedule) + public function getScheduleWithTasks(int $schedule): Schedule { - return $this->getBuilder()->with('tasks')->find($schedule, $this->getColumns()); + /** @var \Pterodactyl\Models\Schedule $instance */ + $instance = $this->getBuilder()->with('tasks')->find($schedule, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; } /** diff --git a/app/Repositories/Eloquent/SubuserRepository.php b/app/Repositories/Eloquent/SubuserRepository.php index 3700371d4..863479934 100644 --- a/app/Repositories/Eloquent/SubuserRepository.php +++ b/app/Repositories/Eloquent/SubuserRepository.php @@ -25,33 +25,39 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI } /** - * {@inheritdoc} + * Return a subuser with the associated server relationship. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @param bool $refresh + * @return \Pterodactyl\Models\Subuser */ - public function getWithServer($id) + public function getWithServer(Subuser $subuser, bool $refresh = false): Subuser { - Assert::numeric($id, 'First argument passed to getWithServer must be numeric, received %s.'); - - $instance = $this->getBuilder()->with('server', 'user')->find($id, $this->getColumns()); - if (! $instance) { - throw new RecordNotFoundException; + if (! $subuser->relationLoaded('server') || $refresh) { + $subuser->load('server'); } - return $instance; + return $subuser; } /** - * {@inheritdoc} + * Return a subuser with the associated permissions relationship. + * + * @param \Pterodactyl\Models\Subuser $subuser + * @param bool $refresh + * @return \Pterodactyl\Models\Subuser */ - public function getWithPermissions($id) + public function getWithPermissions(Subuser $subuser, bool $refresh = false): Subuser { - Assert::numeric($id, 'First argument passed to getWithPermissions must be numeric, received %s.'); - - $instance = $this->getBuilder()->with('permissions', 'user')->find($id, $this->getColumns()); - if (! $instance) { - throw new RecordNotFoundException; + if (! $subuser->relationLoaded('permissions') || $refresh) { + $subuser->load('permissions'); } - return $instance; + if (! $subuser->relationLoaded('user') || $refresh) { + $subuser->load('user'); + } + + return $subuser; } /** diff --git a/app/Services/Subusers/SubuserDeletionService.php b/app/Services/Subusers/SubuserDeletionService.php index 6a9914c0e..f754a5221 100644 --- a/app/Services/Subusers/SubuserDeletionService.php +++ b/app/Services/Subusers/SubuserDeletionService.php @@ -51,17 +51,13 @@ class SubuserDeletionService /** * Delete a subuser and their associated permissions from the Panel and Daemon. * - * @param int|\Pterodactyl\Models\Subuser $subuser + * @param \Pterodactyl\Models\Subuser $subuser * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($subuser) + public function handle(Subuser $subuser) { - if (! $subuser instanceof Subuser) { - $subuser = $this->repository->find($subuser); - } - $this->connection->beginTransaction(); $this->keyDeletionService->handle($subuser->server_id, $subuser->user_id); $this->repository->delete($subuser->id); diff --git a/app/Services/Subusers/SubuserUpdateService.php b/app/Services/Subusers/SubuserUpdateService.php index 5cf6a6576..f4cd2c1a7 100644 --- a/app/Services/Subusers/SubuserUpdateService.php +++ b/app/Services/Subusers/SubuserUpdateService.php @@ -9,13 +9,13 @@ namespace Pterodactyl\Services\Subusers; -use Illuminate\Log\Writer; +use Pterodactyl\Models\Subuser; use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class SubuserUpdateService @@ -23,12 +23,12 @@ class SubuserUpdateService /** * @var \Illuminate\Database\ConnectionInterface */ - protected $connection; + private $connection; /** * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface */ - protected $daemonRepository; + private $daemonRepository; /** * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService @@ -38,22 +38,17 @@ class SubuserUpdateService /** * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface */ - protected $permissionRepository; + private $permissionRepository; /** * @var \Pterodactyl\Services\Subusers\PermissionCreationService */ - protected $permissionService; + private $permissionService; /** * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface */ - protected $repository; - - /** - * @var \Illuminate\Log\Writer - */ - protected $writer; + private $repository; /** * SubuserUpdateService constructor. @@ -64,7 +59,6 @@ class SubuserUpdateService * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $permissionRepository * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository - * @param \Illuminate\Log\Writer $writer */ public function __construct( ConnectionInterface $connection, @@ -72,8 +66,7 @@ class SubuserUpdateService DaemonServerRepositoryInterface $daemonRepository, PermissionCreationService $permissionService, PermissionRepositoryInterface $permissionRepository, - SubuserRepositoryInterface $repository, - Writer $writer + SubuserRepositoryInterface $repository ) { $this->connection = $connection; $this->daemonRepository = $daemonRepository; @@ -81,20 +74,19 @@ class SubuserUpdateService $this->permissionRepository = $permissionRepository; $this->permissionService = $permissionService; $this->repository = $repository; - $this->writer = $writer; } /** * Update permissions for a given subuser. * - * @param int $subuser - * @param array $permissions + * @param \Pterodactyl\Models\Subuser $subuser + * @param array $permissions * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($subuser, array $permissions) + public function handle(Subuser $subuser, array $permissions) { $subuser = $this->repository->getWithServer($subuser); @@ -104,15 +96,10 @@ class SubuserUpdateService try { $token = $this->keyProviderService->handle($subuser->server_id, $subuser->user_id, false); - $this->daemonRepository->setNode($subuser->server->node_id)->revokeAccessKey($token); + $this->daemonRepository->setNode($subuser->getRelation('server')->node_id)->revokeAccessKey($token); } catch (RequestException $exception) { $this->connection->rollBack(); - $this->writer->warning($exception); - - $response = $exception->getResponse(); - throw new DisplayException(trans('exceptions.daemon_connection_failed', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ])); + throw new DaemonConnectionException($exception); } $this->connection->commit(); diff --git a/public/js/laroute.js b/public/js/laroute.js index deec07500..3c0ad073f 100644 --- a/public/js/laroute.js +++ b/public/js/laroute.js @@ -6,7 +6,7 @@ absolute: false, rootUrl: 'http://pterodactyl.app', - routes : [{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":"account.security.totp.set","action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":"account.security.totp.disable","action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{location}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/view\/{location}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{host}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getSettings"},{"host":null,"methods":["POST"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\BaseController@postSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{user}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/nodes","name":"admin.servers.new.nodes","action":"Pterodactyl\Http\Controllers\Admin\ServersController@nodes"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details\/container","name":"admin.servers.view.details.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@setContainer"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{server}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["PATCH"],"uri":"admin\/nodes\/view\/{node}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests","name":"admin.nests","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/new","name":"admin.nests.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/view\/{nest}","name":"admin.nests.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/new","name":"admin.nests.egg.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}","name":"admin.nests.egg.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/export","name":"admin.nests.egg.export","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":"admin.nests.egg.variables","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":"admin.nests.egg.scripts","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@index"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/import","name":"admin.nests.egg.import","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@import"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@store"},{"host":null,"methods":["PUT"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":"admin.nests.egg.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{pack}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{pack}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["PATCH"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@totpCheckpoint"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@console"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\ServerController@getSFTP"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\ServerController@getStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAllocation"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/sftp","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsSFTP"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/databases","name":"server.databases.index","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/databases\/password","name":"server.databases.password","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\DownloadController@index"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@directory"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{subuser}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/view\/{subuser}\/delete","name":"server.subusers.delete","action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules","name":"server.schedules","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/new","name":"server.schedules.new","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":"server.schedules.view","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@view"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@store"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@update"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/toggle","name":"server.schedules.toggle","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskToggleController@index"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/delete","name":"server.schedules.delete","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/authenticate\/{token}","name":"api.remote.authenticate","action":"Pterodactyl\Http\Controllers\API\Remote\ValidateKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs","name":"api.remote.eggs","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs\/{uuid}","name":"api.remote.eggs.download","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@download"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/details\/option\/{server}","name":"daemon.option.details","action":"Pterodactyl\Http\Controllers\Daemon\OptionController@details"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"}], + routes : [{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":"account.security.totp.set","action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":"account.security.totp.disable","action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{location}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/view\/{location}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{host}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getSettings"},{"host":null,"methods":["POST"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\BaseController@postSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{user}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/nodes","name":"admin.servers.new.nodes","action":"Pterodactyl\Http\Controllers\Admin\ServersController@nodes"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details\/container","name":"admin.servers.view.details.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@setContainer"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{server}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["PATCH"],"uri":"admin\/nodes\/view\/{node}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests","name":"admin.nests","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/new","name":"admin.nests.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/view\/{nest}","name":"admin.nests.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/new","name":"admin.nests.egg.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}","name":"admin.nests.egg.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/export","name":"admin.nests.egg.export","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":"admin.nests.egg.variables","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":"admin.nests.egg.scripts","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@index"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/import","name":"admin.nests.egg.import","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@import"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@store"},{"host":null,"methods":["PUT"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":"admin.nests.egg.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{pack}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{pack}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["PATCH"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@totpCheckpoint"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@console"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\Settings\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/databases","name":"server.databases.index","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/databases\/password","name":"server.databases.password","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\DownloadController@index"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@directory"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{subuser}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules","name":"server.schedules","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/new","name":"server.schedules.new","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":"server.schedules.view","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@update"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/toggle","name":"server.schedules.toggle","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskToggleController@index"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/authenticate\/{token}","name":"api.remote.authenticate","action":"Pterodactyl\Http\Controllers\API\Remote\ValidateKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs","name":"api.remote.eggs","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs\/{uuid}","name":"api.remote.eggs.download","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@download"},{"host":null,"methods":["POST"],"uri":"api\/remote\/sftp","name":"api.remote.sftp","action":"Pterodactyl\Http\Controllers\API\Remote\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/details\/option\/{server}","name":"daemon.option.details","action":"Pterodactyl\Http\Controllers\Daemon\OptionController@details"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"}], prefix: '', route : function (name, parameters, route) { diff --git a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js b/public/themes/pterodactyl/js/frontend/files/filemanager.min.js index 718735ddf..31c08a18b 100644 --- a/public/themes/pterodactyl/js/frontend/files/filemanager.min.js +++ b/public/themes/pterodactyl/js/frontend/files/filemanager.min.js @@ -1,5 +1,5 @@ 'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i\n \n ';nameBlock.html(attachEditor);var inputField=nameBlock.find('input');var inputLoader=nameBlock.find('.input-loader');inputField.focus();inputField.on('blur keydown',function(e){if(e.type==='keydown'&&e.which===27||e.type==='blur'||e.type==='keydown'&&e.which===13&¤tName===inputField.val()){if(!_.isEmpty(currentLink)){nameBlock.html(currentLink)}else{nameBlock.html(currentName)}inputField.remove();ContextMenu.unbind().run();return}if(e.type==='keydown'&&e.which!==13)return;inputLoader.show();var currentPath=decodeURIComponent(nameBlock.data('path'));$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/rename',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+currentPath+inputField.val()})}).done(function(data){nameBlock.attr('data-name',inputField.val());if(!_.isEmpty(currentLink)){var newLink=currentLink.attr('href');if(nameBlock.parent().data('type')!=='folder'){newLink=newLink.substr(0,newLink.lastIndexOf('/'))+'/'+inputField.val()}currentLink.attr('href',newLink);nameBlock.html(currentLink.html(inputField.val()))}else{nameBlock.html(inputField.val())}inputField.remove()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}nameBlock.addClass('has-error').delay(2000).queue(function(){nameBlock.removeClass('has-error').dequeue()});inputField.popover({animation:true,placement:'top',content:error,title:'Save Error'}).popover('show')}).always(function(){inputLoader.remove();ContextMenu.unbind().run()})})}},{key:'copy',value:function copy(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var currentName=decodeURIComponent(nameBlock.attr('data-name'));var currentPath=decodeURIComponent(nameBlock.data('path'));swal({type:'input',title:'Copy File',text:'Please enter the new path for the copied file below.',showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true,inputValue:''+currentPath+currentName},function(val){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/copy',timeout:10000,data:JSON.stringify({from:''+currentPath+currentName,to:''+val})}).done(function(data){swal({type:'success',title:'',text:'File successfully copied.'});Files.list()}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'',text:error})})})}},{key:'download',value:function download(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var fileName=decodeURIComponent(nameBlock.attr('data-name'));var filePath=decodeURIComponent(nameBlock.data('path'));window.location='/server/'+Pterodactyl.server.uuidShort+'/files/download/'+filePath+fileName}},{key:'delete',value:function _delete(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var delPath=decodeURIComponent(nameBlock.data('path'));var delName=decodeURIComponent(nameBlock.data('name'));swal({type:'warning',title:'',text:'Are you sure you want to delete '+delName+'? There is no reversing this action.',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:[''+delPath+delName]})}).done(function(data){nameBlock.parent().addClass('warning').delay(200).fadeOut();swal({type:'success',title:'File Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occured while attempting to delete this file. Please try again.'})})})}},{key:'toggleMassActions',value:function toggleMassActions(){if($('#file_listing input[type="checkbox"]:checked').length){$('#mass_actions').removeClass('disabled')}else{$('#mass_actions').addClass('disabled')}}},{key:'toggleHighlight',value:function toggleHighlight(event){var parent=$(event.currentTarget);var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$(item).prop('checked',false);parent.removeClass('warning').delay(200)}else{$(item).prop('checked',true);parent.addClass('warning').delay(200)}}},{key:'highlightAll',value:function highlightAll(event){var parent=void 0;var item=$(event.currentTarget).find('input');if($(item).is(':checked')){$('#file_listing input[type=checkbox]').prop('checked',false);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.removeClass('warning').delay(200)})}else{$('#file_listing input[type=checkbox]').prop('checked',true);$('#file_listing input[data-action="addSelection"]').each(function(){parent=$(this).closest('tr');parent.addClass('warning').delay(200)})}}},{key:'deleteSelected',value:function deleteSelected(){var selectedItems=[];var selectedItemsElements=[];var parent=void 0;var nameBlock=void 0;var delLocation=void 0;$('#file_listing input[data-action="addSelection"]:checked').each(function(){parent=$(this).closest('tr');nameBlock=$(parent).find('td[data-identifier="name"]');delLocation=decodeURIComponent(nameBlock.data('path'))+decodeURIComponent(nameBlock.data('name'));selectedItems.push(delLocation);selectedItemsElements.push(parent)});if(selectedItems.length!=0){var formattedItems='';$.each(selectedItems,function(key,value){formattedItems+=''+value+', '});formattedItems=formattedItems.slice(0,-2);swal({type:'warning',title:'',text:'Are you sure you want to delete:'+formattedItems+'? There is no reversing this action.',html:true,showCancelButton:true,showConfirmButton:true,closeOnConfirm:false,showLoaderOnConfirm:true},function(){$.ajax({type:'POST',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/delete',timeout:10000,data:JSON.stringify({items:selectedItems})}).done(function(data){$('#file_listing input:checked').each(function(){$(this).prop('checked',false)});$.each(selectedItemsElements,function(){$(this).addClass('warning').delay(200).fadeOut()});swal({type:'success',title:'Files Deleted'})}).fail(function(jqXHR){console.error(jqXHR);swal({type:'error',title:'Whoops!',html:true,text:'An error occured while attempting to delete these files. Please try again.'})})})}else{swal({type:'warning',title:'',text:'Please select files/folders to delete.'})}}},{key:'decompress',value:function decompress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));swal({title:' Decompressing...',text:'This might take a few seconds to complete.',html:true,allowOutsideClick:false,allowEscapeKey:false,showConfirmButton:false});$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/decompress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName})}).done(function(data){swal.close();Files.list(compPath)}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}},{key:'compress',value:function compress(){var nameBlock=$(this.element).find('td[data-identifier="name"]');var compPath=decodeURIComponent(nameBlock.data('path'));var compName=decodeURIComponent(nameBlock.data('name'));$.ajax({type:'POST',url:Pterodactyl.node.scheme+'://'+Pterodactyl.node.fqdn+':'+Pterodactyl.node.daemonListen+'/v1/server/file/compress',headers:{'X-Access-Token':Pterodactyl.server.daemonSecret,'X-Access-Server':Pterodactyl.server.uuid},contentType:'application/json; charset=utf-8',data:JSON.stringify({files:''+compPath+compName,to:compPath.toString()})}).done(function(data){Files.list(compPath,function(err){if(err)return;var fileListing=$('#file_listing').find('[data-name="'+data.saved_as+'"]').parent();fileListing.addClass('success pulsate').delay(3000).queue(function(){fileListing.removeClass('success pulsate').dequeue()})})}).fail(function(jqXHR){console.error(jqXHR);var error='An error occured while trying to process this request.';if(typeof jqXHR.responseJSON!=='undefined'&&typeof jqXHR.responseJSON.error!=='undefined'){error=jqXHR.responseJSON.error}swal({type:'error',title:'Whoops!',html:true,text:error})})}}]);return ActionsClass}(); 'use strict';var _createClass=function(){function defineProperties(target,props){for(var i=0;i New File
  • New Folder
  • '}if(Pterodactyl.permissions.downloadFiles||Pterodactyl.permissions.deleteFiles){buildMenu+='
  • '}if(Pterodactyl.permissions.downloadFiles){buildMenu+=''}if(Pterodactyl.permissions.deleteFiles){buildMenu+='
  • Delete
  • '}buildMenu+='';return buildMenu}},{key:'rightClick',value:function rightClick(){var _this=this;$('[data-action="toggleMenu"]').on('mousedown',function(event){event.preventDefault();if($(document).find('#fileOptionMenu').is(':visible')){$('body').trigger('click');return}_this.showMenu(event)});$('#file_listing > tbody td').on('contextmenu',function(event){_this.showMenu(event)})}},{key:'showMenu',value:function showMenu(event){var _this2=this;var parent=$(event.target).closest('tr');var menu=$(this.makeMenu(parent));if(parent.data('type')==='disabled')return;event.preventDefault();$(menu).appendTo('body');$(menu).data('invokedOn',$(event.target)).show().css({position:'absolute',left:event.pageX-150,top:event.pageY});this.activeLine=parent;this.activeLine.addClass('active');var Actions=new ActionsClass(parent,menu);if(Pterodactyl.permissions.moveFiles){$(menu).find('li[data-action="move"]').unbind().on('click',function(e){e.preventDefault();Actions.move()});$(menu).find('li[data-action="rename"]').unbind().on('click',function(e){e.preventDefault();Actions.rename()})}if(Pterodactyl.permissions.copyFiles){$(menu).find('li[data-action="copy"]').unbind().on('click',function(e){e.preventDefault();Actions.copy()})}if(Pterodactyl.permissions.compressFiles){if(parent.data('type')==='folder'){$(menu).find('li[data-action="compress"]').removeClass('hidden')}$(menu).find('li[data-action="compress"]').unbind().on('click',function(e){e.preventDefault();Actions.compress()})}if(Pterodactyl.permissions.decompressFiles){if(_.without(['application/zip','application/gzip','application/x-gzip'],parent.data('mime')).length<3){$(menu).find('li[data-action="decompress"]').removeClass('hidden')}$(menu).find('li[data-action="decompress"]').unbind().on('click',function(e){e.preventDefault();Actions.decompress()})}if(Pterodactyl.permissions.createFiles){$(menu).find('li[data-action="folder"]').unbind().on('click',function(e){e.preventDefault();Actions.folder()})}if(Pterodactyl.permissions.downloadFiles){if(parent.data('type')==='file'){$(menu).find('li[data-action="download"]').removeClass('hidden')}$(menu).find('li[data-action="download"]').unbind().on('click',function(e){e.preventDefault();Actions.download()})}if(Pterodactyl.permissions.deleteFiles){$(menu).find('li[data-action="delete"]').unbind().on('click',function(e){e.preventDefault();Actions.delete()})}$(window).unbind().on('click',function(event){if($(event.target).is('.disable-menu-hide')){event.preventDefault();return}$(menu).unbind().remove();if(!_.isNull(_this2.activeLine))_this2.activeLine.removeClass('active')})}},{key:'directoryClick',value:function directoryClick(){$('a[data-action="directory-view"]').on('click',function(event){event.preventDefault();var path=$(this).parent().data('path')||'';var name=$(this).parent().data('name')||'';window.location.hash=encodeURIComponent(path+name);Files.list()})}}]);return ContextMenuClass}();window.ContextMenu=new ContextMenuClass; -'use strict';var _typeof=typeof Symbol==='function'&&typeof Symbol.iterator==='symbol'?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==='function'&&obj.constructor===Symbol&&obj!==Symbol.prototype?'symbol':typeof obj};var _createClass=function(){function defineProperties(target,props){for(var i=0;i\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass ActionsClass {\n constructor(element, menu) {\n this.element = element;\n this.menu = menu;\n }\n\n destroy() {\n this.element = undefined;\n }\n\n folder(path) {\n let inputValue\n if (path) {\n inputValue = path\n } else {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.data('name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n if ($(this.element).data('type') === 'file') {\n inputValue = currentPath;\n } else {\n inputValue = `${currentPath}${currentName}/`;\n }\n }\n\n swal({\n type: 'input',\n title: 'Create Folder',\n text: 'Please enter the path and folder name below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: inputValue\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/folder`,\n timeout: 10000,\n data: JSON.stringify({\n path: val,\n }),\n }).done(data => {\n swal.close();\n Files.list();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n }\n\n move() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n swal({\n type: 'input',\n title: 'Move File',\n text: 'Please enter the new path for the file below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: `${currentPath}${currentName}`,\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/move`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${val}`,\n }),\n }).done(data => {\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\n swal.close();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n\n }\n\n rename() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentLink = nameBlock.find('a');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const attachEditor = `\n \n \n `;\n\n nameBlock.html(attachEditor);\n const inputField = nameBlock.find('input');\n const inputLoader = nameBlock.find('.input-loader');\n\n inputField.focus();\n inputField.on('blur keydown', e => {\n // Save Field\n if (\n (e.type === 'keydown' && e.which === 27)\n || e.type === 'blur'\n || (e.type === 'keydown' && e.which === 13 && currentName === inputField.val())\n ) {\n if (!_.isEmpty(currentLink)) {\n nameBlock.html(currentLink);\n } else {\n nameBlock.html(currentName);\n }\n inputField.remove();\n ContextMenu.unbind().run();\n return;\n }\n\n if (e.type === 'keydown' && e.which !== 13) return;\n\n inputLoader.show();\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/rename`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${currentPath}${inputField.val()}`,\n }),\n }).done(data => {\n nameBlock.attr('data-name', inputField.val());\n if (!_.isEmpty(currentLink)) {\n let newLink = currentLink.attr('href');\n if (nameBlock.parent().data('type') !== 'folder') {\n newLink = newLink.substr(0, newLink.lastIndexOf('/')) + '/' + inputField.val();\n }\n currentLink.attr('href', newLink);\n nameBlock.html(\n currentLink.html(inputField.val())\n );\n } else {\n nameBlock.html(inputField.val());\n }\n inputField.remove();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n nameBlock.addClass('has-error').delay(2000).queue(() => {\n nameBlock.removeClass('has-error').dequeue();\n });\n inputField.popover({\n animation: true,\n placement: 'top',\n content: error,\n title: 'Save Error'\n }).popover('show');\n }).always(() => {\n inputLoader.remove();\n ContextMenu.unbind().run();\n });\n });\n }\n\n copy() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n swal({\n type: 'input',\n title: 'Copy File',\n text: 'Please enter the new path for the copied file below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: `${currentPath}${currentName}`,\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/copy`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${val}`,\n }),\n }).done(data => {\n swal({\n type: 'success',\n title: '',\n text: 'File successfully copied.'\n });\n Files.list();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n }\n\n download() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const fileName = decodeURIComponent(nameBlock.attr('data-name'));\n const filePath = decodeURIComponent(nameBlock.data('path'));\n\n window.location = `/server/${Pterodactyl.server.uuidShort}/files/download/${filePath}${fileName}`;\n }\n\n delete() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const delPath = decodeURIComponent(nameBlock.data('path'));\n const delName = decodeURIComponent(nameBlock.data('name'));\n\n swal({\n type: 'warning',\n title: '',\n text: 'Are you sure you want to delete ' + delName + '? There is no reversing this action.',\n html: true,\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true\n }, () => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\n timeout: 10000,\n data: JSON.stringify({\n items: [`${delPath}${delName}`]\n }),\n }).done(data => {\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\n swal({\n type: 'success',\n title: 'File Deleted'\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: 'An error occured while attempting to delete this file. Please try again.',\n });\n });\n });\n }\n\n toggleMassActions() {\n if ($('#file_listing input[type=\"checkbox\"]:checked').length) {\n $('#mass_actions').removeClass('disabled');\n } else {\n $('#mass_actions').addClass('disabled');\n }\n }\n\n toggleHighlight(event) {\n const parent = $(event.currentTarget);\n const item = $(event.currentTarget).find('input');\n\n if($(item).is(':checked')) {\n $(item).prop('checked', false);\n parent.removeClass('warning').delay(200);\n } else {\n $(item).prop('checked', true);\n parent.addClass('warning').delay(200);\n }\n }\n\n highlightAll(event) {\n let parent;\n const item = $(event.currentTarget).find('input');\n\n if($(item).is(':checked')) {\n $('#file_listing input[type=checkbox]').prop('checked', false);\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\n parent = $(this).closest('tr');\n parent.removeClass('warning').delay(200);\n });\n } else {\n $('#file_listing input[type=checkbox]').prop('checked', true);\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\n parent = $(this).closest('tr');\n parent.addClass('warning').delay(200);\n });\n }\n }\n\n deleteSelected() {\n let selectedItems = [];\n let selectedItemsElements = [];\n let parent;\n let nameBlock;\n let delLocation;\n\n $('#file_listing input[data-action=\"addSelection\"]:checked').each(function() {\n parent = $(this).closest('tr');\n nameBlock = $(parent).find('td[data-identifier=\"name\"]');\n delLocation = decodeURIComponent(nameBlock.data('path')) + decodeURIComponent(nameBlock.data('name'));\n\n selectedItems.push(delLocation);\n selectedItemsElements.push(parent);\n });\n\n if (selectedItems.length != 0)\n {\n let formattedItems = \"\";\n $.each(selectedItems, function(key, value) {\n formattedItems += (\"\" + value + \", \");\n })\n\n formattedItems = formattedItems.slice(0, -2);\n\n swal({\n type: 'warning',\n title: '',\n text: 'Are you sure you want to delete:' + formattedItems + '? There is no reversing this action.',\n html: true,\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true\n }, () => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\n timeout: 10000,\n data: JSON.stringify({\n items: selectedItems\n }),\n }).done(data => {\n $('#file_listing input:checked').each(function() {\n $(this).prop('checked', false);\n });\n\n $.each(selectedItemsElements, function() {\n $(this).addClass('warning').delay(200).fadeOut();\n })\n\n swal({\n type: 'success',\n title: 'Files Deleted'\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: 'An error occured while attempting to delete these files. Please try again.',\n });\n });\n });\n } else {\n swal({\n type: 'warning',\n title: '',\n text: 'Please select files/folders to delete.',\n });\n }\n }\n\n decompress() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const compPath = decodeURIComponent(nameBlock.data('path'));\n const compName = decodeURIComponent(nameBlock.data('name'));\n\n swal({\n title: ' Decompressing...',\n text: 'This might take a few seconds to complete.',\n html: true,\n allowOutsideClick: false,\n allowEscapeKey: false,\n showConfirmButton: false,\n });\n\n $.ajax({\n type: 'POST',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/decompress`,\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n files: `${compPath}${compName}`\n })\n }).done(data => {\n swal.close();\n Files.list(compPath);\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: error\n });\n });\n }\n\n compress() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const compPath = decodeURIComponent(nameBlock.data('path'));\n const compName = decodeURIComponent(nameBlock.data('name'));\n\n $.ajax({\n type: 'POST',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/compress`,\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n files: `${compPath}${compName}`,\n to: compPath.toString()\n })\n }).done(data => {\n Files.list(compPath, err => {\n if (err) return;\n const fileListing = $('#file_listing').find(`[data-name=\"${data.saved_as}\"]`).parent();\n fileListing.addClass('success pulsate').delay(3000).queue(() => {\n fileListing.removeClass('success pulsate').dequeue();\n });\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: error\n });\n });\n }\n}\n","\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass ContextMenuClass {\n constructor() {\n this.activeLine = null;\n }\n\n run() {\n this.directoryClick();\n this.rightClick();\n }\n\n makeMenu(parent) {\n $(document).find('#fileOptionMenu').remove();\n if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\n\n let newFilePath = $('#file_listing').data('current-dir');\n if (parent.data('type') === 'folder') {\n const nameBlock = parent.find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n newFilePath = `${currentPath}${currentName}`;\n }\n\n let buildMenu = '
      ';\n\n if (Pterodactyl.permissions.moveFiles) {\n buildMenu += '
    • Rename
    • \\\n
    • Move
    • ';\n }\n\n if (Pterodactyl.permissions.copyFiles) {\n buildMenu += '
    • Copy
    • ';\n }\n\n if (Pterodactyl.permissions.compressFiles) {\n buildMenu += '
    • Compress
    • ';\n }\n\n if (Pterodactyl.permissions.decompressFiles) {\n buildMenu += '
    • Decompress
    • ';\n }\n\n if (Pterodactyl.permissions.createFiles) {\n buildMenu += '
    • \\\n
    • New File
    • \\\n
    • New Folder
    • ';\n }\n\n if (Pterodactyl.permissions.downloadFiles || Pterodactyl.permissions.deleteFiles) {\n buildMenu += '
    • ';\n }\n\n if (Pterodactyl.permissions.downloadFiles) {\n buildMenu += '
    • Download
    • ';\n }\n\n if (Pterodactyl.permissions.deleteFiles) {\n buildMenu += '
    • Delete
    • ';\n }\n\n buildMenu += '
    ';\n return buildMenu;\n }\n\n rightClick() {\n $('[data-action=\"toggleMenu\"]').on('mousedown', event => {\n event.preventDefault();\n if ($(document).find('#fileOptionMenu').is(':visible')) {\n $('body').trigger('click');\n return;\n }\n this.showMenu(event);\n });\n $('#file_listing > tbody td').on('contextmenu', event => {\n this.showMenu(event);\n });\n }\n\n showMenu(event) {\n const parent = $(event.target).closest('tr');\n const menu = $(this.makeMenu(parent));\n\n if (parent.data('type') === 'disabled') return;\n event.preventDefault();\n\n $(menu).appendTo('body');\n $(menu).data('invokedOn', $(event.target)).show().css({\n position: 'absolute',\n left: event.pageX - 150,\n top: event.pageY,\n });\n\n this.activeLine = parent;\n this.activeLine.addClass('active');\n\n // Handle Events\n const Actions = new ActionsClass(parent, menu);\n if (Pterodactyl.permissions.moveFiles) {\n $(menu).find('li[data-action=\"move\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.move();\n });\n $(menu).find('li[data-action=\"rename\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.rename();\n });\n }\n\n if (Pterodactyl.permissions.copyFiles) {\n $(menu).find('li[data-action=\"copy\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.copy();\n });\n }\n\n if (Pterodactyl.permissions.compressFiles) {\n if (parent.data('type') === 'folder') {\n $(menu).find('li[data-action=\"compress\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"compress\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.compress();\n });\n }\n\n if (Pterodactyl.permissions.decompressFiles) {\n if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) {\n $(menu).find('li[data-action=\"decompress\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"decompress\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.decompress();\n });\n }\n\n if (Pterodactyl.permissions.createFiles) {\n $(menu).find('li[data-action=\"folder\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.folder();\n });\n }\n\n if (Pterodactyl.permissions.downloadFiles) {\n if (parent.data('type') === 'file') {\n $(menu).find('li[data-action=\"download\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"download\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.download();\n });\n }\n\n if (Pterodactyl.permissions.deleteFiles) {\n $(menu).find('li[data-action=\"delete\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.delete();\n });\n }\n\n $(window).unbind().on('click', event => {\n if($(event.target).is('.disable-menu-hide')) {\n event.preventDefault();\n return;\n }\n $(menu).unbind().remove();\n if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\n });\n }\n\n directoryClick() {\n $('a[data-action=\"directory-view\"]').on('click', function (event) {\n event.preventDefault();\n\n const path = $(this).parent().data('path') || '';\n const name = $(this).parent().data('name') || '';\n\n window.location.hash = encodeURIComponent(path + name);\n Files.list();\n });\n }\n}\n\nwindow.ContextMenu = new ContextMenuClass;\n","\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass FileManager {\n constructor() {\n this.list(this.decodeHash());\n }\n\n list(path, next) {\n if (_.isUndefined(path)) {\n path = this.decodeHash();\n }\n\n this.loader(true);\n $.ajax({\n type: 'POST',\n url: Pterodactyl.meta.directoryList,\n headers: {\n 'X-CSRF-Token': Pterodactyl.meta.csrftoken,\n },\n data: {\n directory: path,\n },\n }).done(data => {\n this.loader(false);\n $('#load_files').slideUp(10).html(data).slideDown(10, () => {\n ContextMenu.run();\n this.reloadFilesButton();\n this.addFolderButton();\n this.selectItem();\n this.selectAll();\n this.selectiveDeletion();\n this.selectRow();\n if (_.isFunction(next)) {\n return next();\n }\n });\n $('#internal_alert').slideUp();\n\n if (typeof Siofu === 'object') {\n Siofu.listenOnInput(document.getElementById(\"files_touch_target\"));\n }\n }).fail(jqXHR => {\n this.loader(false);\n if (_.isFunction(next)) {\n return next(new Error('Failed to load file listing.'));\n }\n swal({\n type: 'error',\n title: 'File Error',\n text: jqXHR.responseText || 'An error occured while attempting to process this request. Please try again.',\n });\n console.error(jqXHR);\n });\n }\n\n loader(show) {\n if (show){\n $('.file-overlay').fadeIn(100);\n } else {\n $('.file-overlay').fadeOut(100);\n }\n }\n\n reloadFilesButton() {\n $('i[data-action=\"reload-files\"]').unbind().on('click', () => {\n $('i[data-action=\"reload-files\"]').addClass('fa-spin');\n this.list();\n });\n }\n\n selectItem() {\n $('[data-action=\"addSelection\"]').on('click', event => {\n event.preventDefault();\n });\n }\n\n selectAll() {\n $('[data-action=\"selectAll\"]').on('click', event => {\n event.preventDefault();\n });\n }\n\n selectiveDeletion() {\n $('[data-action=\"selective-deletion\"]').on('mousedown', event => {\n new ActionsClass().deleteSelected();\n });\n }\n\n addFolderButton() {\n $('[data-action=\"add-folder\"]').unbind().on('click', () => {\n new ActionsClass().folder($('#file_listing').data('current-dir') || '/');\n })\n }\n\n selectRow() {\n $('#file_listing tr').on('mousedown', event => {\n if (event.which === 1) {\n if ($(event.target).is('th') || $(event.target).is('input[data-action=\"selectAll\"]')) {\n new ActionsClass().highlightAll(event);\n } else if ($(event.target).is('td') || $(event.target).is('input[data-action=\"addSelection\"]')) {\n new ActionsClass().toggleHighlight(event);\n }\n\n new ActionsClass().toggleMassActions();\n }\n });\n }\n\n decodeHash() {\n return decodeURIComponent(window.location.hash.substring(1));\n }\n\n}\n\nwindow.Files = new FileManager;\n"]} \ No newline at end of file +{"version":3,"sources":["src/actions.js","src/contextmenu.js","src/index.js"],"names":[],"mappings":"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","file":"filemanager.min.js","sourcesContent":["\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass ActionsClass {\n constructor(element, menu) {\n this.element = element;\n this.menu = menu;\n }\n\n destroy() {\n this.element = undefined;\n }\n\n folder(path) {\n let inputValue\n if (path) {\n inputValue = path\n } else {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.data('name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n if ($(this.element).data('type') === 'file') {\n inputValue = currentPath;\n } else {\n inputValue = `${currentPath}${currentName}/`;\n }\n }\n\n swal({\n type: 'input',\n title: 'Create Folder',\n text: 'Please enter the path and folder name below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: inputValue\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/folder`,\n timeout: 10000,\n data: JSON.stringify({\n path: val,\n }),\n }).done(data => {\n swal.close();\n Files.list();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n }\n\n move() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n swal({\n type: 'input',\n title: 'Move File',\n text: 'Please enter the new path for the file below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: `${currentPath}${currentName}`,\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/move`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${val}`,\n }),\n }).done(data => {\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\n swal.close();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n\n }\n\n rename() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentLink = nameBlock.find('a');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const attachEditor = `\n \n \n `;\n\n nameBlock.html(attachEditor);\n const inputField = nameBlock.find('input');\n const inputLoader = nameBlock.find('.input-loader');\n\n inputField.focus();\n inputField.on('blur keydown', e => {\n // Save Field\n if (\n (e.type === 'keydown' && e.which === 27)\n || e.type === 'blur'\n || (e.type === 'keydown' && e.which === 13 && currentName === inputField.val())\n ) {\n if (!_.isEmpty(currentLink)) {\n nameBlock.html(currentLink);\n } else {\n nameBlock.html(currentName);\n }\n inputField.remove();\n ContextMenu.unbind().run();\n return;\n }\n\n if (e.type === 'keydown' && e.which !== 13) return;\n\n inputLoader.show();\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/rename`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${currentPath}${inputField.val()}`,\n }),\n }).done(data => {\n nameBlock.attr('data-name', inputField.val());\n if (!_.isEmpty(currentLink)) {\n let newLink = currentLink.attr('href');\n if (nameBlock.parent().data('type') !== 'folder') {\n newLink = newLink.substr(0, newLink.lastIndexOf('/')) + '/' + inputField.val();\n }\n currentLink.attr('href', newLink);\n nameBlock.html(\n currentLink.html(inputField.val())\n );\n } else {\n nameBlock.html(inputField.val());\n }\n inputField.remove();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n nameBlock.addClass('has-error').delay(2000).queue(() => {\n nameBlock.removeClass('has-error').dequeue();\n });\n inputField.popover({\n animation: true,\n placement: 'top',\n content: error,\n title: 'Save Error'\n }).popover('show');\n }).always(() => {\n inputLoader.remove();\n ContextMenu.unbind().run();\n });\n });\n }\n\n copy() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n\n swal({\n type: 'input',\n title: 'Copy File',\n text: 'Please enter the new path for the copied file below.',\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true,\n inputValue: `${currentPath}${currentName}`,\n }, (val) => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/copy`,\n timeout: 10000,\n data: JSON.stringify({\n from: `${currentPath}${currentName}`,\n to: `${val}`,\n }),\n }).done(data => {\n swal({\n type: 'success',\n title: '',\n text: 'File successfully copied.'\n });\n Files.list();\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: '',\n text: error,\n });\n });\n });\n }\n\n download() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const fileName = decodeURIComponent(nameBlock.attr('data-name'));\n const filePath = decodeURIComponent(nameBlock.data('path'));\n\n window.location = `/server/${Pterodactyl.server.uuidShort}/files/download/${filePath}${fileName}`;\n }\n\n delete() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const delPath = decodeURIComponent(nameBlock.data('path'));\n const delName = decodeURIComponent(nameBlock.data('name'));\n\n swal({\n type: 'warning',\n title: '',\n text: 'Are you sure you want to delete ' + delName + '? There is no reversing this action.',\n html: true,\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true\n }, () => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\n timeout: 10000,\n data: JSON.stringify({\n items: [`${delPath}${delName}`]\n }),\n }).done(data => {\n nameBlock.parent().addClass('warning').delay(200).fadeOut();\n swal({\n type: 'success',\n title: 'File Deleted'\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: 'An error occured while attempting to delete this file. Please try again.',\n });\n });\n });\n }\n\n toggleMassActions() {\n if ($('#file_listing input[type=\"checkbox\"]:checked').length) {\n $('#mass_actions').removeClass('disabled');\n } else {\n $('#mass_actions').addClass('disabled');\n }\n }\n\n toggleHighlight(event) {\n const parent = $(event.currentTarget);\n const item = $(event.currentTarget).find('input');\n\n if($(item).is(':checked')) {\n $(item).prop('checked', false);\n parent.removeClass('warning').delay(200);\n } else {\n $(item).prop('checked', true);\n parent.addClass('warning').delay(200);\n }\n }\n\n highlightAll(event) {\n let parent;\n const item = $(event.currentTarget).find('input');\n\n if($(item).is(':checked')) {\n $('#file_listing input[type=checkbox]').prop('checked', false);\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\n parent = $(this).closest('tr');\n parent.removeClass('warning').delay(200);\n });\n } else {\n $('#file_listing input[type=checkbox]').prop('checked', true);\n $('#file_listing input[data-action=\"addSelection\"]').each(function() {\n parent = $(this).closest('tr');\n parent.addClass('warning').delay(200);\n });\n }\n }\n\n deleteSelected() {\n let selectedItems = [];\n let selectedItemsElements = [];\n let parent;\n let nameBlock;\n let delLocation;\n\n $('#file_listing input[data-action=\"addSelection\"]:checked').each(function() {\n parent = $(this).closest('tr');\n nameBlock = $(parent).find('td[data-identifier=\"name\"]');\n delLocation = decodeURIComponent(nameBlock.data('path')) + decodeURIComponent(nameBlock.data('name'));\n\n selectedItems.push(delLocation);\n selectedItemsElements.push(parent);\n });\n\n if (selectedItems.length != 0)\n {\n let formattedItems = \"\";\n $.each(selectedItems, function(key, value) {\n formattedItems += (\"\" + value + \", \");\n })\n\n formattedItems = formattedItems.slice(0, -2);\n\n swal({\n type: 'warning',\n title: '',\n text: 'Are you sure you want to delete:' + formattedItems + '? There is no reversing this action.',\n html: true,\n showCancelButton: true,\n showConfirmButton: true,\n closeOnConfirm: false,\n showLoaderOnConfirm: true\n }, () => {\n $.ajax({\n type: 'POST',\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/delete`,\n timeout: 10000,\n data: JSON.stringify({\n items: selectedItems\n }),\n }).done(data => {\n $('#file_listing input:checked').each(function() {\n $(this).prop('checked', false);\n });\n\n $.each(selectedItemsElements, function() {\n $(this).addClass('warning').delay(200).fadeOut();\n })\n\n swal({\n type: 'success',\n title: 'Files Deleted'\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: 'An error occured while attempting to delete these files. Please try again.',\n });\n });\n });\n } else {\n swal({\n type: 'warning',\n title: '',\n text: 'Please select files/folders to delete.',\n });\n }\n }\n\n decompress() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const compPath = decodeURIComponent(nameBlock.data('path'));\n const compName = decodeURIComponent(nameBlock.data('name'));\n\n swal({\n title: ' Decompressing...',\n text: 'This might take a few seconds to complete.',\n html: true,\n allowOutsideClick: false,\n allowEscapeKey: false,\n showConfirmButton: false,\n });\n\n $.ajax({\n type: 'POST',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/decompress`,\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n files: `${compPath}${compName}`\n })\n }).done(data => {\n swal.close();\n Files.list(compPath);\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: error\n });\n });\n }\n\n compress() {\n const nameBlock = $(this.element).find('td[data-identifier=\"name\"]');\n const compPath = decodeURIComponent(nameBlock.data('path'));\n const compName = decodeURIComponent(nameBlock.data('name'));\n\n $.ajax({\n type: 'POST',\n url: `${Pterodactyl.node.scheme}://${Pterodactyl.node.fqdn}:${Pterodactyl.node.daemonListen}/v1/server/file/compress`,\n headers: {\n 'X-Access-Token': Pterodactyl.server.daemonSecret,\n 'X-Access-Server': Pterodactyl.server.uuid,\n },\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n files: `${compPath}${compName}`,\n to: compPath.toString()\n })\n }).done(data => {\n Files.list(compPath, err => {\n if (err) return;\n const fileListing = $('#file_listing').find(`[data-name=\"${data.saved_as}\"]`).parent();\n fileListing.addClass('success pulsate').delay(3000).queue(() => {\n fileListing.removeClass('success pulsate').dequeue();\n });\n });\n }).fail(jqXHR => {\n console.error(jqXHR);\n var error = 'An error occured while trying to process this request.';\n if (typeof jqXHR.responseJSON !== 'undefined' && typeof jqXHR.responseJSON.error !== 'undefined') {\n error = jqXHR.responseJSON.error;\n }\n swal({\n type: 'error',\n title: 'Whoops!',\n html: true,\n text: error\n });\n });\n }\n}\n","\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass ContextMenuClass {\n constructor() {\n this.activeLine = null;\n }\n\n run() {\n this.directoryClick();\n this.rightClick();\n }\n\n makeMenu(parent) {\n $(document).find('#fileOptionMenu').remove();\n if (!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\n\n let newFilePath = $('#file_listing').data('current-dir');\n if (parent.data('type') === 'folder') {\n const nameBlock = parent.find('td[data-identifier=\"name\"]');\n const currentName = decodeURIComponent(nameBlock.attr('data-name'));\n const currentPath = decodeURIComponent(nameBlock.data('path'));\n newFilePath = `${currentPath}${currentName}`;\n }\n\n let buildMenu = '
      ';\n\n if (Pterodactyl.permissions.moveFiles) {\n buildMenu += '
    • Rename
    • \\\n
    • Move
    • ';\n }\n\n if (Pterodactyl.permissions.copyFiles) {\n buildMenu += '
    • Copy
    • ';\n }\n\n if (Pterodactyl.permissions.compressFiles) {\n buildMenu += '
    • Compress
    • ';\n }\n\n if (Pterodactyl.permissions.decompressFiles) {\n buildMenu += '
    • Decompress
    • ';\n }\n\n if (Pterodactyl.permissions.createFiles) {\n buildMenu += '
    • \\\n
    • New File
    • \\\n
    • New Folder
    • ';\n }\n\n if (Pterodactyl.permissions.downloadFiles || Pterodactyl.permissions.deleteFiles) {\n buildMenu += '
    • ';\n }\n\n if (Pterodactyl.permissions.downloadFiles) {\n buildMenu += '
    • Download
    • ';\n }\n\n if (Pterodactyl.permissions.deleteFiles) {\n buildMenu += '
    • Delete
    • ';\n }\n\n buildMenu += '
    ';\n return buildMenu;\n }\n\n rightClick() {\n $('[data-action=\"toggleMenu\"]').on('mousedown', event => {\n event.preventDefault();\n if ($(document).find('#fileOptionMenu').is(':visible')) {\n $('body').trigger('click');\n return;\n }\n this.showMenu(event);\n });\n $('#file_listing > tbody td').on('contextmenu', event => {\n this.showMenu(event);\n });\n }\n\n showMenu(event) {\n const parent = $(event.target).closest('tr');\n const menu = $(this.makeMenu(parent));\n\n if (parent.data('type') === 'disabled') return;\n event.preventDefault();\n\n $(menu).appendTo('body');\n $(menu).data('invokedOn', $(event.target)).show().css({\n position: 'absolute',\n left: event.pageX - 150,\n top: event.pageY,\n });\n\n this.activeLine = parent;\n this.activeLine.addClass('active');\n\n // Handle Events\n const Actions = new ActionsClass(parent, menu);\n if (Pterodactyl.permissions.moveFiles) {\n $(menu).find('li[data-action=\"move\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.move();\n });\n $(menu).find('li[data-action=\"rename\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.rename();\n });\n }\n\n if (Pterodactyl.permissions.copyFiles) {\n $(menu).find('li[data-action=\"copy\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.copy();\n });\n }\n\n if (Pterodactyl.permissions.compressFiles) {\n if (parent.data('type') === 'folder') {\n $(menu).find('li[data-action=\"compress\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"compress\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.compress();\n });\n }\n\n if (Pterodactyl.permissions.decompressFiles) {\n if (_.without(['application/zip', 'application/gzip', 'application/x-gzip'], parent.data('mime')).length < 3) {\n $(menu).find('li[data-action=\"decompress\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"decompress\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.decompress();\n });\n }\n\n if (Pterodactyl.permissions.createFiles) {\n $(menu).find('li[data-action=\"folder\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.folder();\n });\n }\n\n if (Pterodactyl.permissions.downloadFiles) {\n if (parent.data('type') === 'file') {\n $(menu).find('li[data-action=\"download\"]').removeClass('hidden');\n }\n $(menu).find('li[data-action=\"download\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.download();\n });\n }\n\n if (Pterodactyl.permissions.deleteFiles) {\n $(menu).find('li[data-action=\"delete\"]').unbind().on('click', e => {\n e.preventDefault();\n Actions.delete();\n });\n }\n\n $(window).unbind().on('click', event => {\n if($(event.target).is('.disable-menu-hide')) {\n event.preventDefault();\n return;\n }\n $(menu).unbind().remove();\n if(!_.isNull(this.activeLine)) this.activeLine.removeClass('active');\n });\n }\n\n directoryClick() {\n $('a[data-action=\"directory-view\"]').on('click', function (event) {\n event.preventDefault();\n\n const path = $(this).parent().data('path') || '';\n const name = $(this).parent().data('name') || '';\n\n window.location.hash = encodeURIComponent(path + name);\n Files.list();\n });\n }\n}\n\nwindow.ContextMenu = new ContextMenuClass;\n","\"use strict\";\n\n// Copyright (c) 2015 - 2017 Dane Everitt \n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\nclass FileManager {\n constructor() {\n this.list(this.decodeHash());\n }\n\n list(path, next) {\n if (_.isUndefined(path)) {\n path = this.decodeHash();\n }\n\n this.loader(true);\n $.ajax({\n type: 'POST',\n url: Pterodactyl.meta.directoryList,\n headers: {\n 'X-CSRF-Token': Pterodactyl.meta.csrftoken,\n },\n data: {\n directory: path,\n },\n }).done(data => {\n this.loader(false);\n $('#load_files').slideUp(10).html(data).slideDown(10, () => {\n ContextMenu.run();\n this.reloadFilesButton();\n this.addFolderButton();\n this.selectItem();\n this.selectAll();\n this.selectiveDeletion();\n this.selectRow();\n if (_.isFunction(next)) {\n return next();\n }\n });\n $('#internal_alert').slideUp();\n\n if (typeof Siofu === 'object') {\n Siofu.listenOnInput(document.getElementById(\"files_touch_target\"));\n }\n }).fail(jqXHR => {\n this.loader(false);\n if (_.isFunction(next)) {\n return next(new Error('Failed to load file listing.'));\n }\n swal({\n type: 'error',\n title: 'File Error',\n text: jqXHR.responseJSON.error || 'An error occured while attempting to process this request. Please try again.',\n });\n console.error(jqXHR);\n });\n }\n\n loader(show) {\n if (show){\n $('.file-overlay').fadeIn(100);\n } else {\n $('.file-overlay').fadeOut(100);\n }\n }\n\n reloadFilesButton() {\n $('i[data-action=\"reload-files\"]').unbind().on('click', () => {\n $('i[data-action=\"reload-files\"]').addClass('fa-spin');\n this.list();\n });\n }\n\n selectItem() {\n $('[data-action=\"addSelection\"]').on('click', event => {\n event.preventDefault();\n });\n }\n\n selectAll() {\n $('[data-action=\"selectAll\"]').on('click', event => {\n event.preventDefault();\n });\n }\n\n selectiveDeletion() {\n $('[data-action=\"selective-deletion\"]').on('mousedown', event => {\n new ActionsClass().deleteSelected();\n });\n }\n\n addFolderButton() {\n $('[data-action=\"add-folder\"]').unbind().on('click', () => {\n new ActionsClass().folder($('#file_listing').data('current-dir') || '/');\n })\n }\n\n selectRow() {\n $('#file_listing tr').on('mousedown', event => {\n if (event.which === 1) {\n if ($(event.target).is('th') || $(event.target).is('input[data-action=\"selectAll\"]')) {\n new ActionsClass().highlightAll(event);\n } else if ($(event.target).is('td') || $(event.target).is('input[data-action=\"addSelection\"]')) {\n new ActionsClass().toggleHighlight(event);\n }\n\n new ActionsClass().toggleMassActions();\n }\n });\n }\n\n decodeHash() {\n return decodeURIComponent(window.location.hash.substring(1));\n }\n\n}\n\nwindow.Files = new FileManager;\n"]} \ No newline at end of file diff --git a/public/themes/pterodactyl/js/frontend/files/src/index.js b/public/themes/pterodactyl/js/frontend/files/src/index.js index 79b6d80fd..3b3f6993b 100644 --- a/public/themes/pterodactyl/js/frontend/files/src/index.js +++ b/public/themes/pterodactyl/js/frontend/files/src/index.js @@ -66,7 +66,7 @@ class FileManager { swal({ type: 'error', title: 'File Error', - text: jqXHR.responseText || 'An error occured while attempting to process this request. Please try again.', + text: jqXHR.responseJSON.error || 'An error occured while attempting to process this request. Please try again.', }); console.error(jqXHR); }); diff --git a/public/themes/pterodactyl/js/frontend/tasks/management-actions.js b/public/themes/pterodactyl/js/frontend/tasks/management-actions.js index 9a5706a31..60a8194bf 100644 --- a/public/themes/pterodactyl/js/frontend/tasks/management-actions.js +++ b/public/themes/pterodactyl/js/frontend/tasks/management-actions.js @@ -34,7 +34,7 @@ $(document).ready(function () { }, function () { $.ajax({ method: 'DELETE', - url: Router.route('server.schedules.delete', { + url: Router.route('server.schedules.view', { server: Pterodactyl.server.uuidShort, schedule: self.data('schedule-id'), }), diff --git a/resources/themes/pterodactyl/server/users/index.blade.php b/resources/themes/pterodactyl/server/users/index.blade.php index 475cb411b..1cb88233f 100644 --- a/resources/themes/pterodactyl/server/users/index.blade.php +++ b/resources/themes/pterodactyl/server/users/index.blade.php @@ -57,14 +57,14 @@ {{ $subuser->user->created_at }} @can('view-subuser', $server) - + @endcan @can('delete-subuser', $server) - + @@ -98,9 +98,9 @@ }, function () { $.ajax({ method: 'DELETE', - url: Router.route('server.subusers.delete', { + url: Router.route('server.subusers.view', { server: Pterodactyl.server.uuidShort, - id: self.data('id'), + subuser: self.data('id'), }), headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), diff --git a/resources/themes/pterodactyl/server/users/view.blade.php b/resources/themes/pterodactyl/server/users/view.blade.php index b29f2cd37..f175bb44e 100644 --- a/resources/themes/pterodactyl/server/users/view.blade.php +++ b/resources/themes/pterodactyl/server/users/view.blade.php @@ -21,7 +21,7 @@ @section('content') @can('edit-subuser', $server) -
    + @endcan
    diff --git a/routes/server.php b/routes/server.php index f6333a20d..5945f4fd6 100644 --- a/routes/server.php +++ b/routes/server.php @@ -70,13 +70,13 @@ Route::group(['prefix' => 'files'], function () { Route::group(['prefix' => 'users'], function () { Route::get('/', 'SubuserController@index')->name('server.subusers'); Route::get('/new', 'SubuserController@create')->name('server.subusers.new'); - Route::get('/view/{subuser}', 'SubuserController@view')->middleware('server..subuser')->name('server.subusers.view'); - Route::post('/new', 'SubuserController@store'); - Route::patch('/view/{subuser}', 'SubuserController@update')->middleware('server..subuser'); - - Route::delete('/view/{subuser}/delete', 'SubuserController@delete')->middleware('server..subuser')->name('server.subusers.delete'); + Route::group(['middleware' => 'server..subuser'], function () { + Route::get('/view/{subuser}', 'SubuserController@view')->name('server.subusers.view'); + Route::patch('/view/{subuser}', 'SubuserController@update'); + Route::delete('/view/{subuser}', 'SubuserController@delete'); + }); }); /* @@ -90,12 +90,14 @@ Route::group(['prefix' => 'users'], function () { Route::group(['prefix' => 'schedules'], function () { Route::get('/', 'Tasks\TaskManagementController@index')->name('server.schedules'); Route::get('/new', 'Tasks\TaskManagementController@create')->name('server.schedules.new'); - Route::get('/view/{schedule}', 'Tasks\TaskManagementController@view')->middleware('server..schedule')->name('server.schedules.view'); - Route::post('/new', 'Tasks\TaskManagementController@store'); - Route::patch('/view/{schedule}', 'Tasks\TaskManagementController@update')->middleware('server..schedule'); - Route::patch('/view/{schedule}/toggle', 'Tasks\TaskToggleController@index')->middleware('server..schedule')->name('server.schedules.toggle'); + Route::group(['middleware' => 'server..schedule'], function () { + Route::get('/view/{schedule}', 'Tasks\TaskManagementController@view')->name('server.schedules.view'); - Route::delete('/view/{schedule}/delete', 'Tasks\TaskManagementController@delete')->middleware('server..schedule')->name('server.schedules.delete'); + Route::patch('/view/{schedule}', 'Tasks\TaskManagementController@update'); + Route::patch('/view/{schedule}/toggle', 'Tasks\TaskToggleController@index')->name('server.schedules.toggle'); + + Route::delete('/view/{schedule}', 'Tasks\TaskManagementController@delete'); + }); }); From 79decafdc8341481b940e00648cb6ae956244561 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 29 Oct 2017 12:37:25 -0500 Subject: [PATCH 02/10] Update all the middlewares --- CHANGELOG.md | 1 + app/Http/Kernel.php | 3 +- app/Http/Middleware/AdminAuthenticate.php | 16 ++--- app/Http/Middleware/Authenticate.php | 3 +- .../Middleware/Daemon/DaemonAuthenticate.php | 9 ++- app/Http/Middleware/DaemonAuthenticate.php | 38 ++++++----- app/Http/Middleware/EncryptCookies.php | 3 +- app/Http/Middleware/LanguageMiddleware.php | 32 +++++---- .../Middleware/RedirectIfAuthenticated.php | 22 ++++++- .../RequireTwoFactorAuthentication.php | 38 ++++++----- .../AuthenticateAsSubuser.php} | 21 +++--- .../Server/ScheduleBelongsToServer.php | 3 +- .../Server/SubuserBelongsToServer.php | 3 +- app/Http/Middleware/VerifyReCaptcha.php | 66 ++++++++++++------- config/app.php | 2 +- resources/lang/en/auth.php | 1 + 16 files changed, 161 insertions(+), 100 deletions(-) rename app/Http/Middleware/{SubuserAccessAuthenticate.php => Server/AuthenticateAsSubuser.php} (80%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e44b65b9b..1451f159b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. * Server creation page now only asks for a node to deploy to, rather than requiring a location and then a node. * Database passwords are now hidden by default and will only show if clicked on. In addition, database view in ACP now indicates that passwords must be viewed on the front-end. * Localhost cannot be used as a connection address in the environment configuration script. `127.0.0.1` is allowed. +* Application locale can now be quickly set using an environment variable `APP_LOCALE` rather than having to edit core files. ### Fixed * Unable to change the daemon secret for a server via the Admin CP. diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index e5bcb5d61..74c72ece1 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -6,6 +6,7 @@ use Pterodactyl\Http\Middleware\DaemonAuthenticate; use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Routing\Middleware\SubstituteBindings; use Pterodactyl\Http\Middleware\AccessingValidServer; +use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser; use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer; use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; @@ -66,7 +67,7 @@ class Kernel extends HttpKernel 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class, 'server' => AccessingValidServer::class, - 'subuser.auth' => \Pterodactyl\Http\Middleware\SubuserAccessAuthenticate::class, + 'subuser.auth' => AuthenticateAsSubuser::class, 'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class, 'daemon-old' => DaemonAuthenticate::class, 'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php index e5c34da33..7c47ed0c1 100644 --- a/app/Http/Middleware/AdminAuthenticate.php +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -10,6 +10,8 @@ namespace Pterodactyl\Http\Middleware; use Closure; +use Illuminate\Http\Request; +use Symfony\Component\HttpKernel\Exception\HttpException; class AdminAuthenticate { @@ -20,18 +22,10 @@ class AdminAuthenticate * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { - if (! $request->user()) { - if ($request->expectsJson() || $request->json()) { - return response('Unauthorized.', 401); - } else { - return redirect()->guest('auth/login'); - } - } - - if (! $request->user()->root_admin) { - return abort(403); + if (! $request->user() || ! $request->user()->root_admin) { + throw new HttpException(403, 'Access Denied'); } return $next($request); diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 06e2d6b70..019f92a2f 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Middleware; use Closure; +use Illuminate\Http\Request; use Illuminate\Contracts\Auth\Guard; class Authenticate @@ -31,7 +32,7 @@ class Authenticate * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { if ($this->auth->guest()) { if ($request->ajax()) { diff --git a/app/Http/Middleware/Daemon/DaemonAuthenticate.php b/app/Http/Middleware/Daemon/DaemonAuthenticate.php index 2572ba854..ab587cbe0 100644 --- a/app/Http/Middleware/Daemon/DaemonAuthenticate.php +++ b/app/Http/Middleware/Daemon/DaemonAuthenticate.php @@ -33,9 +33,12 @@ use Pterodactyl\Exceptions\Repository\RecordNotFoundException; class DaemonAuthenticate { /** + * Daemon routes that this middleware should be skipped on. * @var array */ - protected $except = ['daemon.configuration']; + protected $except = [ + 'daemon.configuration', + ]; /** * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface @@ -63,6 +66,10 @@ class DaemonAuthenticate */ public function handle(Request $request, Closure $next) { + if (in_array($request->route()->getName(), $this->except)) { + return $next($request); + } + $token = $request->bearerToken(); if (is_null($token)) { diff --git a/app/Http/Middleware/DaemonAuthenticate.php b/app/Http/Middleware/DaemonAuthenticate.php index 056c0b344..ca77e70d2 100644 --- a/app/Http/Middleware/DaemonAuthenticate.php +++ b/app/Http/Middleware/DaemonAuthenticate.php @@ -10,35 +10,36 @@ namespace Pterodactyl\Http\Middleware; use Closure; +use Illuminate\Http\Request; use Pterodactyl\Models\Node; -use Illuminate\Contracts\Auth\Guard; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; class DaemonAuthenticate { - /** - * The Guard implementation. - * - * @var \Illuminate\Contracts\Auth\Guard - */ - protected $auth; - /** * An array of route names to not apply this middleware to. * * @var array */ - protected $except = [ + private $except = [ 'daemon.configuration', ]; + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + private $repository; + /** * Create a new filter instance. * - * @param \Illuminate\Contracts\Auth\Guard $auth + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository */ - public function __construct(Guard $auth) + public function __construct(NodeRepositoryInterface $repository) { - $this->auth = $auth; + $this->repository = $repository; } /** @@ -48,21 +49,24 @@ class DaemonAuthenticate * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { if (in_array($request->route()->getName(), $this->except)) { return $next($request); } if (! $request->header('X-Access-Node')) { - return abort(403); + throw new HttpException(403); } - $node = Node::where('daemonSecret', $request->header('X-Access-Node'))->first(); - if (! $node) { - return abort(401); + try { + $node = $this->repository->findWhere(['daemonSecret' => $request->header('X-Access-Node')]); + } catch (RecordNotFoundException $exception) { + throw new HttpException(401); } + $request->attributes->set('node', $node); + return $next($request); } } diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index eefb3359f..9c0cadd86 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -11,6 +11,5 @@ class EncryptCookies extends BaseEncrypter * * @var array */ - protected $except = [ - ]; + protected $except = []; } diff --git a/app/Http/Middleware/LanguageMiddleware.php b/app/Http/Middleware/LanguageMiddleware.php index 786760baf..d348f3b8d 100644 --- a/app/Http/Middleware/LanguageMiddleware.php +++ b/app/Http/Middleware/LanguageMiddleware.php @@ -9,14 +9,28 @@ namespace Pterodactyl\Http\Middleware; -use Auth; use Closure; -use Session; -use Settings; +use Illuminate\Http\Request; use Illuminate\Support\Facades\App; +use Illuminate\Contracts\Config\Repository; class LanguageMiddleware { + /** + * @var \Illuminate\Contracts\Config\Repository + */ + private $config; + + /** + * LanguageMiddleware constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + */ + public function __construct(Repository $config) + { + $this->config = $config; + } + /** * Handle an incoming request. * @@ -24,17 +38,9 @@ class LanguageMiddleware * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { - // if (Session::has('applocale')) { - // App::setLocale(Session::get('applocale')); - // } elseif (Auth::check() && isset(Auth::user()->language)) { - // Session::put('applocale', Auth::user()->language); - // App::setLocale(Auth::user()->language); - // } else { - // App::setLocale(Settings::get('default_language', 'en')); - // } - App::setLocale('en'); + App::setLocale($this->config->get('app.locale', 'en')); return $next($request); } diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index a25e05fb2..ae55fef92 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -3,10 +3,26 @@ namespace Pterodactyl\Http\Middleware; use Closure; -use Illuminate\Support\Facades\Auth; +use Illuminate\Http\Request; +use Illuminate\Auth\AuthManager; class RedirectIfAuthenticated { + /** + * @var \Illuminate\Contracts\Auth\Guard + */ + private $authManager; + + /** + * RedirectIfAuthenticated constructor. + * + * @param \Illuminate\Auth\AuthManager $authManager + */ + public function __construct(AuthManager $authManager) + { + $this->authManager = $authManager; + } + /** * Handle an incoming request. * @@ -15,9 +31,9 @@ class RedirectIfAuthenticated * @param string|null $guard * @return mixed */ - public function handle($request, Closure $next, $guard = null) + public function handle(Request $request, Closure $next, string $guard = null) { - if (Auth::guard($guard)->check()) { + if ($this->authManager->guard($guard)->check()) { return redirect(route('index')); } diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index e53412b9c..75fb01664 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -10,6 +10,7 @@ namespace Pterodactyl\Http\Middleware; use Closure; +use Illuminate\Http\Request; use Krucas\Settings\Settings; use Prologue\Alerts\AlertsMessageBag; @@ -22,28 +23,35 @@ class RequireTwoFactorAuthentication /** * @var \Prologue\Alerts\AlertsMessageBag */ - protected $alert; + private $alert; /** * @var \Krucas\Settings\Settings */ - protected $settings; + private $settings; /** - * All TOTP related routes. + * The names of routes that should be accessable without 2FA enabled. * * @var array */ - protected $ignoreRoutes = [ - 'account.security', - 'account.security.revoke', - 'account.security.totp', - 'account.security.totp.set', - 'account.security.totp.disable', - 'auth.totp', - 'auth.logout', + protected $except = [ + 'account.security', + 'account.security.revoke', + 'account.security.totp', + 'account.security.totp.set', + 'account.security.totp.disable', + 'auth.totp', + 'auth.logout', ]; + /** + * The route to redirect a user to to enable 2FA. + * + * @var string + */ + protected $redirectRoute = 'account.security'; + /** * RequireTwoFactorAuthentication constructor. * @@ -63,7 +71,7 @@ class RequireTwoFactorAuthentication * @param \Closure $next * @return mixed */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { // Ignore non-users if (! $request->user()) { @@ -71,7 +79,7 @@ class RequireTwoFactorAuthentication } // Skip the 2FA pages - if (in_array($request->route()->getName(), $this->ignoreRoutes)) { + if (in_array($request->route()->getName(), $this->except)) { return $next($request); } @@ -93,8 +101,8 @@ class RequireTwoFactorAuthentication break; } - $this->alert->danger('The administrator has required 2FA to be enabled. You must enable it before you can do any other action.')->flash(); + $this->alert->danger(trans('auth.2fa_must_be_enabled'))->flash(); - return redirect()->route('account.security'); + return redirect()->route($this->redirectRoute); } } diff --git a/app/Http/Middleware/SubuserAccessAuthenticate.php b/app/Http/Middleware/Server/AuthenticateAsSubuser.php similarity index 80% rename from app/Http/Middleware/SubuserAccessAuthenticate.php rename to app/Http/Middleware/Server/AuthenticateAsSubuser.php index 30a906884..47f2bc885 100644 --- a/app/Http/Middleware/SubuserAccessAuthenticate.php +++ b/app/Http/Middleware/Server/AuthenticateAsSubuser.php @@ -7,7 +7,7 @@ * https://opensource.org/licenses/MIT */ -namespace Pterodactyl\Http\Middleware; +namespace Pterodactyl\Http\Middleware\Server; use Closure; use Illuminate\Http\Request; @@ -16,17 +16,17 @@ use Illuminate\Auth\AuthenticationException; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -class SubuserAccessAuthenticate +class AuthenticateAsSubuser { /** * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService */ - protected $keyProviderService; + private $keyProviderService; /** * @var \Illuminate\Contracts\Session\Session */ - protected $session; + private $session; /** * SubuserAccessAuthenticate constructor. @@ -34,10 +34,8 @@ class SubuserAccessAuthenticate * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService * @param \Illuminate\Contracts\Session\Session $session */ - public function __construct( - DaemonKeyProviderService $keyProviderService, - Session $session - ) { + public function __construct(DaemonKeyProviderService $keyProviderService, Session $session) + { $this->keyProviderService = $keyProviderService; $this->session = $session; } @@ -55,16 +53,17 @@ class SubuserAccessAuthenticate */ public function handle(Request $request, Closure $next) { - $server = $this->session->get('server_data.model'); + $server = $request->attributes->get('server'); try { $token = $this->keyProviderService->handle($server->id, $request->user()->id); - $this->session->now('server_data.token', $token); - $request->attributes->set('server_token', $token); } catch (RecordNotFoundException $exception) { throw new AuthenticationException('This account does not have permission to access this server.'); } + $this->session->now('server_data.token', $token); + $request->attributes->set('server_token', $token); + return $next($request); } } diff --git a/app/Http/Middleware/Server/ScheduleBelongsToServer.php b/app/Http/Middleware/Server/ScheduleBelongsToServer.php index 32205b6ba..f9f40bf3b 100644 --- a/app/Http/Middleware/Server/ScheduleBelongsToServer.php +++ b/app/Http/Middleware/Server/ScheduleBelongsToServer.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Middleware\Server; use Closure; +use Illuminate\Http\Request; use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -41,7 +42,7 @@ class ScheduleBelongsToServer * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { $server = $request->attributes->get('server'); diff --git a/app/Http/Middleware/Server/SubuserBelongsToServer.php b/app/Http/Middleware/Server/SubuserBelongsToServer.php index 100144c16..18291245d 100644 --- a/app/Http/Middleware/Server/SubuserBelongsToServer.php +++ b/app/Http/Middleware/Server/SubuserBelongsToServer.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Middleware\Server; use Closure; +use Illuminate\Http\Request; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; @@ -43,7 +44,7 @@ class SubuserBelongsToServer * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ - public function handle($request, Closure $next) + public function handle(Request $request, Closure $next) { $server = $request->attributes->get('server'); diff --git a/app/Http/Middleware/VerifyReCaptcha.php b/app/Http/Middleware/VerifyReCaptcha.php index 07a0783c7..83a78fcd2 100644 --- a/app/Http/Middleware/VerifyReCaptcha.php +++ b/app/Http/Middleware/VerifyReCaptcha.php @@ -3,28 +3,46 @@ namespace Pterodactyl\Http\Middleware; use Closure; +use GuzzleHttp\Client; +use Illuminate\Http\Request; use Pterodactyl\Events\Auth\FailedCaptcha; +use Illuminate\Contracts\Config\Repository; class VerifyReCaptcha { + /** + * @var \Illuminate\Contracts\Config\Repository + */ + private $config; + + /** + * VerifyReCaptcha constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + */ + public function __construct(Repository $config) + { + $this->config = $config; + } + /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next - * @return \Illuminate\Http\RediectResponse + * @return \Illuminate\Http\RedirectResponse|mixed */ public function handle($request, Closure $next) { - if (! config('recaptcha.enabled')) { + if (! $this->config->get('recaptcha.enabled')) { return $next($request); } if ($request->has('g-recaptcha-response')) { - $client = new \GuzzleHttp\Client(); - $res = $client->post(config('recaptcha.domain'), [ + $client = new Client(); + $res = $client->post($this->config->get('recaptcha.domain'), [ 'form_params' => [ - 'secret' => config('recaptcha.secret_key'), + 'secret' => $this->config->get('recaptcha.secret_key'), 'response' => $request->input('g-recaptcha-response'), ], ]); @@ -32,29 +50,33 @@ class VerifyReCaptcha if ($res->getStatusCode() === 200) { $result = json_decode($res->getBody()); - $verified = function ($result, $request) { - if (! config('recaptcha.verify_domain')) { - return false; - } - - $url = parse_url($request->url()); - - if (! array_key_exists('host', $url)) { - return false; - } - - return $result->hostname === $url['host']; - }; - - if ($result->success && (! config('recaptcha.verify_domain') || $verified($result, $request))) { + if ($result->success && (! $this->config->get('recaptcha.verify_domain') || $this->isResponseVerified($result, $request))) { return $next($request); } } } // Emit an event and return to the previous view with an error (only the captcha error will be shown!) - event(new FailedCaptcha($request->ip(), (! isset($result->hostname) ?: $result->hostname))); + event(new FailedCaptcha($request->ip(), (! isset($result) ?: object_get($result, 'hostname')))); - return back()->withErrors(['g-recaptcha-response' => trans('strings.captcha_invalid')])->withInput(); + return redirect()->back()->withErrors(['g-recaptcha-response' => trans('strings.captcha_invalid')])->withInput(); + } + + /** + * Determine if the response from the recaptcha servers was valid. + * + * @param object $result + * @param \Illuminate\Http\Request $request + * @return bool + */ + private function isResponseVerified(object $result, Request $request): bool + { + if (! $this->config->get('recaptcha.verify_domain')) { + return false; + } + + $url = parse_url($request->url()); + + return $result->hostname === array_get($url, 'host'); } } diff --git a/config/app.php b/config/app.php index 928e1657a..7eb416d37 100644 --- a/config/app.php +++ b/config/app.php @@ -66,7 +66,7 @@ return [ | */ - 'locale' => 'en', + 'locale' => env('APP_LOCALE', 'en'), /* |-------------------------------------------------------------------------- diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index ebaee6243..1abd6bd73 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -18,4 +18,5 @@ return [ '2fa_required' => '2-Factor Authentication', '2fa_failed' => 'The 2FA token provided was invalid.', 'totp_failed' => 'There was an error while attempting to validate TOTP.', + '2fa_must_be_enabled' => 'The administrator has required that 2-Factor Authentication be enabled for your account in order to use the Panel.', ]; From e9aecfe6db54a0c75040fc35498bcbda41fbd146 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 29 Oct 2017 15:57:43 -0500 Subject: [PATCH 03/10] Shorten imports --- app/Http/Kernel.php | 66 ++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 74c72ece1..718d7eddf 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,14 +2,32 @@ namespace Pterodactyl\Http; +use Fideloper\Proxy\TrustProxies; +use Illuminate\Auth\Middleware\Authorize; +use Illuminate\Auth\Middleware\Authenticate; +use Pterodactyl\Http\Middleware\TrimStrings; +use Illuminate\Session\Middleware\StartSession; +use Pterodactyl\Http\Middleware\EncryptCookies; +use Pterodactyl\Http\Middleware\VerifyCsrfToken; +use Pterodactyl\Http\Middleware\VerifyReCaptcha; +use Pterodactyl\Http\Middleware\AdminAuthenticate; +use Pterodactyl\Http\Middleware\HMACAuthorization; +use Illuminate\Routing\Middleware\ThrottleRequests; use Pterodactyl\Http\Middleware\DaemonAuthenticate; +use Pterodactyl\Http\Middleware\LanguageMiddleware; use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Routing\Middleware\SubstituteBindings; use Pterodactyl\Http\Middleware\AccessingValidServer; +use Illuminate\View\Middleware\ShareErrorsFromSession; +use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; +use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; +use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser; use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer; +use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; +use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; class Kernel extends HttpKernel { @@ -19,15 +37,15 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, - \Pterodactyl\Http\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Pterodactyl\Http\Middleware\TrimStrings::class, + CheckForMaintenanceMode::class, + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + TrimStrings::class, /* * Custom middleware applied to all routes. */ - \Fideloper\Proxy\TrustProxies::class, + TrustProxies::class, ]; /** @@ -37,23 +55,23 @@ class Kernel extends HttpKernel */ protected $middlewareGroups = [ 'web' => [ - \Pterodactyl\Http\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - \Illuminate\View\Middleware\ShareErrorsFromSession::class, - \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, - \Illuminate\Routing\Middleware\SubstituteBindings::class, - \Pterodactyl\Http\Middleware\LanguageMiddleware::class, - \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication::class, + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + LanguageMiddleware::class, + RequireTwoFactorAuthentication::class, ], 'api' => [ - \Pterodactyl\Http\Middleware\HMACAuthorization::class, + HMACAuthorization::class, 'throttle:60,1', 'bindings', ], 'daemon' => [ - \Pterodactyl\Http\Middleware\Daemon\DaemonAuthenticate::class, SubstituteBindings::class, + 'daemon-old', ], ]; @@ -63,18 +81,18 @@ class Kernel extends HttpKernel * @var array */ protected $routeMiddleware = [ - 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, - 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, - 'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class, + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'guest' => RedirectIfAuthenticated::class, 'server' => AccessingValidServer::class, 'subuser.auth' => AuthenticateAsSubuser::class, - 'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class, + 'admin' => AdminAuthenticate::class, 'daemon-old' => DaemonAuthenticate::class, - 'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, - 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'can' => \Illuminate\Auth\Middleware\Authorize::class, - 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, - 'recaptcha' => \Pterodactyl\Http\Middleware\VerifyReCaptcha::class, + 'csrf' => VerifyCsrfToken::class, + 'throttle' => ThrottleRequests::class, + 'can' => Authorize::class, + 'bindings' => SubstituteBindings::class, + 'recaptcha' => VerifyReCaptcha::class, // Server specific middleware (used for authenticating access to resources) // From d844a36167de7d735c4e6a0b4599cb8bea9ced2c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 29 Oct 2017 21:40:34 -0500 Subject: [PATCH 04/10] Begin adding unit tests for middleware --- .../Middleware/Daemon/DaemonAuthenticate.php | 11 +- .../Server/AccessingValidServer.php | 22 +-- .../Daemon/DaemonAuthenticateTest.php | 126 ++++++++++++ .../Server/AccessingValidServerTest.php | 185 ++++++++++++++++++ 4 files changed, 324 insertions(+), 20 deletions(-) create mode 100644 tests/Unit/Http/Middleware/Daemon/DaemonAuthenticateTest.php create mode 100644 tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php diff --git a/app/Http/Middleware/Daemon/DaemonAuthenticate.php b/app/Http/Middleware/Daemon/DaemonAuthenticate.php index ab587cbe0..d06711af9 100644 --- a/app/Http/Middleware/Daemon/DaemonAuthenticate.php +++ b/app/Http/Middleware/Daemon/DaemonAuthenticate.php @@ -32,19 +32,20 @@ use Pterodactyl\Exceptions\Repository\RecordNotFoundException; class DaemonAuthenticate { + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + private $repository; + /** * Daemon routes that this middleware should be skipped on. + * * @var array */ protected $except = [ 'daemon.configuration', ]; - /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface - */ - protected $repository; - /** * DaemonAuthenticate constructor. * diff --git a/app/Http/Middleware/Server/AccessingValidServer.php b/app/Http/Middleware/Server/AccessingValidServer.php index 762f9a298..5137d7721 100644 --- a/app/Http/Middleware/Server/AccessingValidServer.php +++ b/app/Http/Middleware/Server/AccessingValidServer.php @@ -6,7 +6,6 @@ use Closure; use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Illuminate\Contracts\Session\Session; -use Illuminate\Auth\AuthenticationException; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -17,22 +16,17 @@ class AccessingValidServer /** * @var \Illuminate\Contracts\Config\Repository */ - protected $config; + private $config; /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface */ - protected $repository; - - /** - * @var \Pterodactyl\Models\Server - */ - protected $server; + private $repository; /** * @var \Illuminate\Contracts\Session\Session */ - protected $session; + private $session; /** * AccessingValidServer constructor. @@ -56,7 +50,7 @@ class AccessingValidServer * * @param \Illuminate\Http\Request $request * @param \Closure $next - * @return mixed + * @return \Illuminate\Http\Response|mixed * * @throws \Illuminate\Auth\AuthenticationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException @@ -65,10 +59,6 @@ class AccessingValidServer */ public function handle(Request $request, Closure $next) { - if (! $request->user()) { - throw new AuthenticationException; - } - $attributes = $request->route()->parameter('server'); $isApiRequest = $request->expectsJson() || $request->is(...$this->config->get('pterodactyl.json_routes', [])); $server = $this->repository->getByUuid($attributes instanceof Server ? $attributes->uuid : $attributes); @@ -89,9 +79,11 @@ class AccessingValidServer return response()->view('errors.suspended', [], 403); } + // Servers can have install statuses other than 1 or 0, so don't check + // for a bool-type operator here. if ($server->installed !== 1) { if ($isApiRequest) { - throw new AccessDeniedHttpException('Server is completing install process.'); + throw new AccessDeniedHttpException('Server is not marked as installed.'); } return response()->view('errors.installing', [], 403); diff --git a/tests/Unit/Http/Middleware/Daemon/DaemonAuthenticateTest.php b/tests/Unit/Http/Middleware/Daemon/DaemonAuthenticateTest.php new file mode 100644 index 000000000..efe667743 --- /dev/null +++ b/tests/Unit/Http/Middleware/Daemon/DaemonAuthenticateTest.php @@ -0,0 +1,126 @@ +repository = m::mock(NodeRepositoryInterface::class); + $this->request = m::mock(Request::class); + $this->request->attributes = new ParameterBag(); + } + + /** + * Test that if we are accessing the daemon.configuration route this middleware is not + * applied in order to allow an unauthenticated request to use a token to grab data. + */ + public function testResponseShouldContinueIfRouteIsExempted() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('daemon.configuration'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that not passing in the bearer token will result in a HTTP/401 error with the + * proper response headers. + */ + public function testResponseShouldFailIfNoTokenIsProvided() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturnNull(); + + try { + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } catch (HttpException $exception) { + $this->assertEquals(401, $exception->getStatusCode(), 'Assert that a status code of 401 is returned.'); + $this->assertTrue(is_array($exception->getHeaders()), 'Assert that an array of headers is returned.'); + $this->assertArrayHasKey('WWW-Authenticate', $exception->getHeaders(), 'Assert exception headers contains WWW-Authenticate.'); + $this->assertEquals('Bearer', $exception->getHeaders()['WWW-Authenticate']); + } + } + + /** + * Test that passing in an invalid node daemon secret will result in a HTTP/403 + * error response. + */ + public function testResponseShouldFailIfNoNodeIsFound() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturn('test1234'); + + $this->repository->shouldReceive('findFirstWhere')->with([['daemonSecret', '=', 'test1234']])->once()->andThrow(new RecordNotFoundException); + + try { + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } catch (HttpException $exception) { + $this->assertEquals(403, $exception->getStatusCode(), 'Assert that a status code of 403 is returned.'); + } + } + + /** + * Test a successful middleware process. + */ + public function testSuccessfulMiddlewareProcess() + { + $model = factory(Node::class)->make(); + + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturn($model->daemonSecret); + + $this->repository->shouldReceive('findFirstWhere')->with([['daemonSecret', '=', $model->daemonSecret]])->once()->andReturn($model); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertTrue($this->request->attributes->has('node'), 'Assert request attributes contains node.'); + $this->assertSame($model, $this->request->attributes->get('node')); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Daemon\DaemonAuthenticate + */ + private function getMiddleware(): DaemonAuthenticate + { + return new DaemonAuthenticate($this->repository); + } + + /** + * Provide a closure to be used when validating that the response from the middleware + * is the same request object we passed into it. + */ + private function getClosureAssertions(): Closure + { + return function ($response) { + $this->assertInstanceOf(Request::class, $response); + $this->assertSame($this->request, $response); + }; + } +} diff --git a/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php b/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php new file mode 100644 index 000000000..0cf27e8a6 --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php @@ -0,0 +1,185 @@ +config = m::mock(Repository::class); + $this->repository = m::mock(ServerRepositoryInterface::class); + $this->request = m::mock(Request::class); + $this->request->attributes = new ParameterBag(); + $this->session = m::mock(Session::class); + } + + /** + * Test that an exception is thrown if the request is an API request and no server is found. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @expectedExceptionMessage The requested server was not found on the system. + */ + public function testExceptionIsThrownIfNoServerIsFoundAndIsAPIRequest() + { + $this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456'); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(true); + + $this->repository->shouldReceive('getByUuid')->with('123456')->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an exception is thrown if the request is an API request and the server is suspended. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @expectedExceptionMessage Server is suspended. + */ + public function testExceptionIsThrownIfServerIsSuspended() + { + $model = factory(Server::class)->make(['suspended' => 1]); + + $this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456'); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(true); + + $this->repository->shouldReceive('getByUuid')->with('123456')->once()->andReturn($model); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an exception is thrown if the request is an API request and the server is not installed. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @expectedExceptionMessage Server is not marked as installed. + */ + public function testExceptionIsThrownIfServerIsNotInstalled() + { + $model = factory(Server::class)->make(['installed' => 0]); + + $this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456'); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(true); + + $this->repository->shouldReceive('getByUuid')->with('123456')->once()->andReturn($model); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that the correct error pages are rendered depending on the status of the server. + * + * @dataProvider viewDataProvider + */ + public function testCorrectErrorPagesAreRendered(Server $model = null, string $page, int $httpCode) + { + $this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456'); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(false); + $this->config->shouldReceive('get')->with('pterodactyl.json_routes', [])->once()->andReturn([]); + $this->request->shouldReceive('is')->with(...[])->once()->andReturn(false); + + $this->repository->shouldReceive('getByUuid')->with('123456')->once()->andReturn($model); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals($page, $response->getOriginalContent()->getName(), 'Assert that the correct view is returned.'); + $this->assertEquals($httpCode, $response->getStatusCode(), 'Assert that the correct HTTP code is returned.'); + } + + /** + * Test that the full middleware works correctly. + */ + public function testValidServerProcess() + { + $model = factory(Server::class)->make(); + + $this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456'); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(false); + $this->config->shouldReceive('get')->with('pterodactyl.json_routes', [])->once()->andReturn([]); + $this->request->shouldReceive('is')->with(...[])->once()->andReturn(false); + + $this->repository->shouldReceive('getByUuid')->with('123456')->once()->andReturn($model); + $this->session->shouldReceive('now')->with('server_data.model', $model)->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertTrue($this->request->attributes->has('server'), 'Assert request attributes contains server.'); + $this->assertSame($model, $this->request->attributes->get('server')); + } + + /** + * Provide test data that checks that the correct view is returned for each model type. + * + * @return array + */ + public function viewDataProvider(): array + { + // Without this we are unable to instantiate the factory builders for some reason. + $this->refreshApplication(); + + return [ + [null, 'errors.404', 404], + [factory(Server::class)->make(['suspended' => 1]), 'errors.suspended', 403], + [factory(Server::class)->make(['installed' => 0]), 'errors.installing', 403], + [factory(Server::class)->make(['installed' => 2]), 'errors.installing', 403], + ]; + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\AccessingValidServer + */ + private function getMiddleware(): AccessingValidServer + { + return new AccessingValidServer($this->config, $this->repository, $this->session); + } + + /** + * Provide a closure to be used when validating that the response from the middleware + * is the same request object we passed into it. + */ + private function getClosureAssertions(): Closure + { + return function ($response) { + $this->assertInstanceOf(Request::class, $response); + $this->assertSame($this->request, $response); + }; + } +} From 7b3393aff9e63445c32ac048c764dc1b7e31593c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 1 Nov 2017 20:45:43 -0500 Subject: [PATCH 05/10] More middleware tests --- .../Server/DatabaseBelongsToServer.php | 4 +- .../Server/SubuserBelongsToServer.php | 2 +- .../MiddlewareAttributeAssertionsTrait.php | 39 +++++ tests/Traits/Http/MocksMiddlewareClosure.php | 31 ++++ .../Daemon/DaemonAuthenticateTest.php | 30 +--- .../Http/Middleware/MiddlewareTestCase.php | 58 +++++++ .../Server/AccessingValidServerTest.php | 31 +--- .../Server/AuthenticateAsSubuserTest.php | 79 +++++++++ .../Server/DatabaseBelongsToServerTest.php | 92 +++++++++++ .../Server/ScheduleBelongsToServerTest.php | 81 +++++++++ .../Server/SubuserBelongsToServerTest.php | 156 ++++++++++++++++++ 11 files changed, 547 insertions(+), 56 deletions(-) create mode 100644 tests/Assertions/MiddlewareAttributeAssertionsTrait.php create mode 100644 tests/Traits/Http/MocksMiddlewareClosure.php create mode 100644 tests/Unit/Http/Middleware/MiddlewareTestCase.php create mode 100644 tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php create mode 100644 tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php create mode 100644 tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php create mode 100644 tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php diff --git a/app/Http/Middleware/Server/DatabaseBelongsToServer.php b/app/Http/Middleware/Server/DatabaseBelongsToServer.php index bc31c29c8..d7e34d211 100644 --- a/app/Http/Middleware/Server/DatabaseBelongsToServer.php +++ b/app/Http/Middleware/Server/DatabaseBelongsToServer.php @@ -12,7 +12,7 @@ class DatabaseBelongsToServer /** * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface */ - protected $repository; + private $repository; /** * DatabaseAccess constructor. @@ -40,7 +40,7 @@ class DatabaseBelongsToServer $server = $request->attributes->get('server'); $database = $this->repository->find($request->input('database')); - if ($database->server_id !== $server->id) { + if (is_null($database) || $database->server_id !== $server->id) { throw new NotFoundHttpException; } diff --git a/app/Http/Middleware/Server/SubuserBelongsToServer.php b/app/Http/Middleware/Server/SubuserBelongsToServer.php index 18291245d..cdcd3f097 100644 --- a/app/Http/Middleware/Server/SubuserBelongsToServer.php +++ b/app/Http/Middleware/Server/SubuserBelongsToServer.php @@ -50,7 +50,7 @@ class SubuserBelongsToServer $hash = $request->route()->parameter('subuser', 0); $subuser = $this->repository->find($this->hashids->decodeFirst($hash, 0)); - if (! $subuser || $subuser->server_id !== $server->id) { + if (is_null($subuser) || $subuser->server_id !== $server->id) { throw new NotFoundHttpException; } diff --git a/tests/Assertions/MiddlewareAttributeAssertionsTrait.php b/tests/Assertions/MiddlewareAttributeAssertionsTrait.php new file mode 100644 index 000000000..fb8f70086 --- /dev/null +++ b/tests/Assertions/MiddlewareAttributeAssertionsTrait.php @@ -0,0 +1,39 @@ +request->attributes->has($attribute), 'Assert that request mock has ' . $attribute . ' attribute.'); + } + + /** + * Assert a request does not have an attribute assigned to it. + * + * @param string $attribute + */ + public function assertRequestMissingAttribute(string $attribute) + { + Assert::assertFalse($this->request->attributes->has($attribute), 'Assert that request mock does not have ' . $attribute . ' attribute.'); + } + + /** + * Assert a request attribute matches an expected value. + * + * @param mixed $expected + * @param string $attribute + */ + public function assertRequestAttributeEquals($expected, string $attribute) + { + Assert::assertEquals($expected, $this->request->attributes->get($attribute)); + } +} diff --git a/tests/Traits/Http/MocksMiddlewareClosure.php b/tests/Traits/Http/MocksMiddlewareClosure.php new file mode 100644 index 000000000..53b922585 --- /dev/null +++ b/tests/Traits/Http/MocksMiddlewareClosure.php @@ -0,0 +1,31 @@ +request)) { + throw new BadFunctionCallException('Calling getClosureAssertions without defining a request object is not supported.'); + } + + return function ($response) { + $this->assertInstanceOf(Request::class, $response); + $this->assertSame($this->request, $response); + }; + } +} diff --git a/tests/Unit/Http/Middleware/Daemon/DaemonAuthenticateTest.php b/tests/Unit/Http/Middleware/Daemon/DaemonAuthenticateTest.php index efe667743..140c07286 100644 --- a/tests/Unit/Http/Middleware/Daemon/DaemonAuthenticateTest.php +++ b/tests/Unit/Http/Middleware/Daemon/DaemonAuthenticateTest.php @@ -2,29 +2,21 @@ namespace Tests\Unit\Http\Middleware\Daemon; -use Closure; use Mockery as m; -use Tests\TestCase; -use Illuminate\Http\Request; use Pterodactyl\Models\Node; -use Symfony\Component\HttpFoundation\ParameterBag; +use Tests\Unit\Http\Middleware\MiddlewareTestCase; use Symfony\Component\HttpKernel\Exception\HttpException; use Pterodactyl\Http\Middleware\Daemon\DaemonAuthenticate; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -class DaemonAuthenticateTest extends TestCase +class DaemonAuthenticateTest extends MiddlewareTestCase { /** * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface|\Mockery\Mock */ private $repository; - /** - * @var \Illuminate\Http\Request|\Mockery\Mock - */ - private $request; - /** * Setup tests. */ @@ -33,8 +25,6 @@ class DaemonAuthenticateTest extends TestCase parent::setUp(); $this->repository = m::mock(NodeRepositoryInterface::class); - $this->request = m::mock(Request::class); - $this->request->attributes = new ParameterBag(); } /** @@ -98,8 +88,8 @@ class DaemonAuthenticateTest extends TestCase $this->repository->shouldReceive('findFirstWhere')->with([['daemonSecret', '=', $model->daemonSecret]])->once()->andReturn($model); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - $this->assertTrue($this->request->attributes->has('node'), 'Assert request attributes contains node.'); - $this->assertSame($model, $this->request->attributes->get('node')); + $this->assertRequestHasAttribute('node'); + $this->assertRequestAttributeEquals($model, 'node'); } /** @@ -111,16 +101,4 @@ class DaemonAuthenticateTest extends TestCase { return new DaemonAuthenticate($this->repository); } - - /** - * Provide a closure to be used when validating that the response from the middleware - * is the same request object we passed into it. - */ - private function getClosureAssertions(): Closure - { - return function ($response) { - $this->assertInstanceOf(Request::class, $response); - $this->assertSame($this->request, $response); - }; - } } diff --git a/tests/Unit/Http/Middleware/MiddlewareTestCase.php b/tests/Unit/Http/Middleware/MiddlewareTestCase.php new file mode 100644 index 000000000..463570f0e --- /dev/null +++ b/tests/Unit/Http/Middleware/MiddlewareTestCase.php @@ -0,0 +1,58 @@ +request = m::mock(Request::class); + $this->request->attributes = new ParameterBag(); + } + + /** + * Set a request attribute on the mock object. + * + * @param string $attribute + * @param mixed $value + */ + protected function setRequestAttribute(string $attribute, $value) + { + $this->request->attributes->set($attribute, $value); + } + + /** + * Sets the mocked request user. If a user model is not provided, a factory model + * will be created and returned. + * + * @param \Pterodactyl\Models\User|null $user + * @return \Pterodactyl\Models\User + */ + protected function setRequestUser(User $user = null): User + { + $user = $user instanceof User ? $user : factory(User::class)->make(); + $this->request->shouldReceive('user')->withNoArgs()->andReturn($user); + + return $user; + } +} diff --git a/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php b/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php index 0cf27e8a6..deed73a69 100644 --- a/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php +++ b/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php @@ -2,20 +2,16 @@ namespace Tests\Unit\Http\Middleware\Server; -use Closure; use Mockery as m; -use Tests\TestCase; -use Illuminate\View\View; -use Illuminate\Http\Request; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Contracts\Session\Session; use Illuminate\Contracts\Config\Repository; -use Symfony\Component\HttpFoundation\ParameterBag; +use Tests\Unit\Http\Middleware\MiddlewareTestCase; use Pterodactyl\Http\Middleware\AccessingValidServer; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -class AccessingValidServerTest extends TestCase +class AccessingValidServerTest extends MiddlewareTestCase { /** * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock @@ -27,11 +23,6 @@ class AccessingValidServerTest extends TestCase */ private $repository; - /** - * @var \Illuminate\Http\Request|\Mockery\Mock - */ - private $request; - /** * @var \Illuminate\Contracts\Session\Session|\Mockery\Mock */ @@ -46,8 +37,6 @@ class AccessingValidServerTest extends TestCase $this->config = m::mock(Repository::class); $this->repository = m::mock(ServerRepositoryInterface::class); - $this->request = m::mock(Request::class); - $this->request->attributes = new ParameterBag(); $this->session = m::mock(Session::class); } @@ -139,8 +128,8 @@ class AccessingValidServerTest extends TestCase $this->session->shouldReceive('now')->with('server_data.model', $model)->once()->andReturnNull(); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - $this->assertTrue($this->request->attributes->has('server'), 'Assert request attributes contains server.'); - $this->assertSame($model, $this->request->attributes->get('server')); + $this->assertRequestHasAttribute('server'); + $this->assertRequestAttributeEquals($model, 'server'); } /** @@ -170,16 +159,4 @@ class AccessingValidServerTest extends TestCase { return new AccessingValidServer($this->config, $this->repository, $this->session); } - - /** - * Provide a closure to be used when validating that the response from the middleware - * is the same request object we passed into it. - */ - private function getClosureAssertions(): Closure - { - return function ($response) { - $this->assertInstanceOf(Request::class, $response); - $this->assertSame($this->request, $response); - }; - } } diff --git a/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php b/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php new file mode 100644 index 000000000..9645ddb6b --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php @@ -0,0 +1,79 @@ +keyProviderService = m::mock(DaemonKeyProviderService::class); + $this->session = m::mock(Session::class); + } + + /** + * Test a successful instance of the middleware. + */ + public function testSuccessfulMiddleware() + { + $model = factory(Server::class)->make(); + $user = $this->setRequestUser(); + $this->setRequestAttribute('server', $model); + + $this->keyProviderService->shouldReceive('handle')->with($model->id, $user->id)->once()->andReturn('abc123'); + $this->session->shouldReceive('now')->with('server_data.token', 'abc123')->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('server_token'); + $this->assertRequestAttributeEquals('abc123', 'server_token'); + } + + /** + * Test middleware handles missing token exception. + * + * @expectedException \Illuminate\Auth\AuthenticationException + * @expectedExceptionMessage This account does not have permission to access this server. + */ + public function testExceptionIsThrownIfNoTokenIsFound() + { + $model = factory(Server::class)->make(); + $user = $this->setRequestUser(); + $this->setRequestAttribute('server', $model); + + $this->keyProviderService->shouldReceive('handle')->with($model->id, $user->id)->once()->andThrow(new RecordNotFoundException); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser + */ + public function getMiddleware(): AuthenticateAsSubuser + { + return new AuthenticateAsSubuser($this->keyProviderService, $this->session); + } +} diff --git a/tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php b/tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php new file mode 100644 index 000000000..856ea1b98 --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php @@ -0,0 +1,92 @@ +repository = m::mock(DatabaseRepositoryInterface::class); + } + + /** + * Test a successful middleware instance. + */ + public function testSuccessfulMiddleware() + { + $model = factory(Server::class)->make(); + $database = factory(Database::class)->make([ + 'server_id' => $model->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('input')->with('database')->once()->andReturn($database->id); + $this->repository->shouldReceive('find')->with($database->id)->once()->andReturn($database); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('database'); + $this->assertRequestAttributeEquals($database, 'database'); + } + + /** + * Test that an exception is thrown if no database record is found. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfNoDatabaseRecordFound() + { + $model = factory(Server::class)->make(); + $database = factory(Database::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('input')->with('database')->once()->andReturn($database->id); + $this->repository->shouldReceive('find')->with($database->id)->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an exception is found if the database server does not match the + * request server. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent() + { + $model = factory(Server::class)->make(); + $database = factory(Database::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('input')->with('database')->once()->andReturn($database->id); + $this->repository->shouldReceive('find')->with($database->id)->once()->andReturn($database); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer + */ + private function getMiddleware(): DatabaseBelongsToServer + { + return new DatabaseBelongsToServer($this->repository); + } +} diff --git a/tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php b/tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php new file mode 100644 index 000000000..755808e06 --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php @@ -0,0 +1,81 @@ +hashids = m::mock(HashidsInterface::class); + $this->repository = m::mock(ScheduleRepositoryInterface::class); + } + + /** + * Test a successful middleware instance. + */ + public function testSuccessfulMiddleware() + { + $model = factory(Server::class)->make(); + $schedule = factory(Schedule::class)->make([ + 'server_id' => $model->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('schedule')->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($schedule->id); + $this->repository->shouldReceive('getScheduleWithTasks')->with($schedule->id)->once()->andReturn($schedule); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('schedule'); + $this->assertRequestAttributeEquals($schedule, 'schedule'); + } + + /** + * Test that an exception is thrown if the schedule does not belong to + * the request server. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfScheduleDoesNotBelongToServer() + { + $model = factory(Server::class)->make(); + $schedule = factory(Schedule::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('schedule')->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($schedule->id); + $this->repository->shouldReceive('getScheduleWithTasks')->with($schedule->id)->once()->andReturn($schedule); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer + */ + private function getMiddleware(): ScheduleBelongsToServer + { + return new ScheduleBelongsToServer($this->hashids, $this->repository); + } +} diff --git a/tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php b/tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php new file mode 100644 index 000000000..a4cd7e4f7 --- /dev/null +++ b/tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php @@ -0,0 +1,156 @@ +hashids = m::mock(HashidsInterface::class); + $this->repository = m::mock(SubuserRepositoryInterface::class); + } + + /** + * Test a successful middleware instance. + */ + public function testSuccessfulMiddleware() + { + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make([ + 'server_id' => $model->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser); + + $this->request->shouldReceive('method')->withNoArgs()->once()->andReturn('GET'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('subuser'); + $this->assertRequestAttributeEquals($subuser, 'subuser'); + } + + /** + * Test that a user can edit a user other than themselves. + */ + public function testSuccessfulMiddlewareWhenPatchRequest() + { + $this->setRequestUser(); + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make([ + 'server_id' => $model->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser); + + $this->request->shouldReceive('method')->withNoArgs()->once()->andReturn('PATCH'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('subuser'); + $this->assertRequestAttributeEquals($subuser, 'subuser'); + } + + /** + * Test that an exception is thrown if a user attempts to edit themself. + */ + public function testExceptionIsThrownIfUserTriesToEditSelf() + { + $user = $this->setRequestUser(); + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make([ + 'server_id' => $model->id, + 'user_id' => $user->id, + ]); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser); + + $this->request->shouldReceive('method')->withNoArgs()->once()->andReturn('PATCH'); + + try { + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DisplayException::class, $exception); + $this->assertEquals(trans('exceptions.subusers.editing_self'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if a subuser server does not match the + * request server. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer() + { + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that an exception is thrown if no subuser is found. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testExceptionIsThrownIfNoSubuserIsFound() + { + $model = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make(); + $this->setRequestAttribute('server', $model); + + $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123'); + $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id); + $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer + */ + private function getMiddleware(): SubuserBelongsToServer + { + return new SubuserBelongsToServer($this->hashids, $this->repository); + } +} From 54228e81243985fb640700425ed6ee49a8a78143 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 3 Nov 2017 16:43:28 -0500 Subject: [PATCH 06/10] Fix multiple controller unit test failures --- .../Controllers/Server/SubuserController.php | 9 +- tests/Traits/Http/RequestMockHelpers.php | 73 ++++++++ .../Http/Controllers/ControllerTestCase.php | 86 ++++++++++ .../Server/ConsoleControllerTest.php | 42 ++--- .../Server/Files/DownloadControllerTest.php | 48 +++--- .../Files/FileActionsControllerTest.php | 128 +++++++------- .../Files/RemoteRequestControllerTest.php | 137 +++++++-------- .../Server/SubuserControllerTest.php | 157 +++++++++--------- .../Http/Middleware/MiddlewareTestCase.php | 41 +---- 9 files changed, 404 insertions(+), 317 deletions(-) create mode 100644 tests/Traits/Http/RequestMockHelpers.php create mode 100644 tests/Unit/Http/Controllers/ControllerTestCase.php diff --git a/app/Http/Controllers/Server/SubuserController.php b/app/Http/Controllers/Server/SubuserController.php index eb09fb63c..00d7b9291 100644 --- a/app/Http/Controllers/Server/SubuserController.php +++ b/app/Http/Controllers/Server/SubuserController.php @@ -128,7 +128,7 @@ class SubuserController extends Controller */ public function update(SubuserUpdateFormRequest $request, string $uuid, string $hash): RedirectResponse { - $this->subuserUpdateService->handle($request->attributes->get('subuser'), $request->input('permissions')); + $this->subuserUpdateService->handle($request->attributes->get('subuser'), $request->input('permissions', [])); $this->alert->success(trans('server.users.user_updated'))->flash(); return redirect()->route('server.subusers.view', ['uuid' => $uuid, 'subuser' => $hash]); @@ -154,7 +154,6 @@ class SubuserController extends Controller * Handles creating a new subuser. * * @param \Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest $request - * @param string $uuid * @return \Illuminate\Http\RedirectResponse * * @throws \Exception @@ -164,15 +163,15 @@ class SubuserController extends Controller * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException */ - public function store(SubuserStoreFormRequest $request, $uuid): RedirectResponse + public function store(SubuserStoreFormRequest $request): RedirectResponse { $server = $request->attributes->get('server'); - $subuser = $this->subuserCreationService->handle($server, $request->input('email'), $request->input('permissions')); + $subuser = $this->subuserCreationService->handle($server, $request->input('email'), $request->input('permissions', [])); $this->alert->success(trans('server.users.user_assigned'))->flash(); return redirect()->route('server.subusers.view', [ - 'uuid' => $uuid, + 'uuid' => $server->uuid, 'id' => $subuser->id, ]); } diff --git a/tests/Traits/Http/RequestMockHelpers.php b/tests/Traits/Http/RequestMockHelpers.php new file mode 100644 index 000000000..d6cc7865a --- /dev/null +++ b/tests/Traits/Http/RequestMockHelpers.php @@ -0,0 +1,73 @@ +requestMockClass = $class; + + $this->buildRequestMock(); + } + + /** + * Set the active request object to be an instance of a mocked request. + */ + protected function buildRequestMock() + { + $this->request = m::mock($this->requestMockClass); + if (! $this->request instanceof Request) { + throw new InvalidArgumentException('First argument passed to buildRequestMock must be an instance of \Illuminate\Http\Request when mocked.'); + } + + $this->request->attributes = new ParameterBag(); + } + + /** + * Set a request attribute on the mock object. + * + * @param string $attribute + * @param mixed $value + */ + protected function setRequestAttribute(string $attribute, $value) + { + $this->request->attributes->set($attribute, $value); + } + + /** + * Sets the mocked request user. If a user model is not provided, a factory model + * will be created and returned. + * + * @param \Pterodactyl\Models\User|null $user + * @return \Pterodactyl\Models\User + */ + protected function setRequestUser(User $user = null): User + { + $user = $user instanceof User ? $user : factory(User::class)->make(); + $this->request->shouldReceive('user')->withNoArgs()->andReturn($user); + + return $user; + } +} diff --git a/tests/Unit/Http/Controllers/ControllerTestCase.php b/tests/Unit/Http/Controllers/ControllerTestCase.php new file mode 100644 index 000000000..6caf9abda --- /dev/null +++ b/tests/Unit/Http/Controllers/ControllerTestCase.php @@ -0,0 +1,86 @@ +buildRequestMock(); + } + + /** + * Set an instance of the controller. + * + * @param \Pterodactyl\Http\Controllers\Controller|\Mockery\Mock $controller + */ + public function setControllerInstance($controller) + { + $this->controller = $controller; + } + + /** + * Return an instance of the controller. + * + * @return \Mockery\Mock|\Pterodactyl\Http\Controllers\Controller + */ + public function getControllerInstance() + { + return $this->controller; + } + + /** + * Helper function to mock injectJavascript requests. + * + * @param array|null $args + * @param bool $subset + */ + protected function mockInjectJavascript(array $args = null, bool $subset = false) + { + $controller = $this->getControllerInstance(); + + $controller->shouldReceive('setRequest')->with($this->request)->once()->andReturnSelf(); + if (is_null($args)) { + $controller->shouldReceive('injectJavascript')->withAnyArgs()->once()->andReturnNull(); + } else { + $with = $subset ? m::subset($args) : $args; + + $controller->shouldReceive('injectJavascript')->with($with)->once()->andReturnNull(); + } + } + + /** + * Build and return a mocked controller instance to use for testing. + * + * @param string $class + * @param array $args + * @return \Mockery\Mock|\Pterodactyl\Http\Controllers\Controller + */ + protected function buildMockedController(string $class, array $args = []) + { + $controller = m::mock($class, $args)->makePartial(); + + if (is_null($this->getControllerInstance())) { + $this->setControllerInstance($controller); + } + + return $this->getControllerInstance(); + } +} diff --git a/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php b/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php index a73cf13e9..ef6334657 100644 --- a/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php +++ b/tests/Unit/Http/Controllers/Server/ConsoleControllerTest.php @@ -10,32 +10,18 @@ namespace Tests\Unit\Http\Controllers\Server; use Mockery as m; -use Tests\TestCase; use Pterodactyl\Models\Server; -use Illuminate\Contracts\Session\Session; use Illuminate\Contracts\Config\Repository; -use Tests\Assertions\ControllerAssertionsTrait; +use Tests\Unit\Http\Controllers\ControllerTestCase; use Pterodactyl\Http\Controllers\Server\ConsoleController; -class ConsoleControllerTest extends TestCase +class ConsoleControllerTest extends ControllerTestCase { - use ControllerAssertionsTrait; - /** - * @var \Illuminate\Contracts\Config\Repository + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock */ protected $config; - /** - * @var \Pterodactyl\Http\Controllers\Server\ConsoleController - */ - protected $controller; - - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - /** * Setup tests. */ @@ -44,9 +30,6 @@ class ConsoleControllerTest extends TestCase parent::setUp(); $this->config = m::mock(Repository::class); - $this->session = m::mock(Session::class); - - $this->controller = m::mock(ConsoleController::class, [$this->config, $this->session])->makePartial(); } /** @@ -56,16 +39,15 @@ class ConsoleControllerTest extends TestCase */ public function testAllControllers($function, $view) { + $controller = $this->getController(); $server = factory(Server::class)->make(); + $this->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); - 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(); - $response = $this->controller->$function(); + $response = $controller->$function($this->request); $this->assertIsViewResponse($response); $this->assertViewNameEquals($view, $response); } @@ -82,4 +64,14 @@ class ConsoleControllerTest extends TestCase ['console', 'server.console'], ]; } + + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\ConsoleController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(ConsoleController::class, [$this->config]); + } } diff --git a/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php b/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php index ac12fcff3..d44481c1b 100644 --- a/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php +++ b/tests/Unit/Http/Controllers/Server/Files/DownloadControllerTest.php @@ -10,34 +10,22 @@ namespace Tests\Unit\Http\Controllers\Server\Files; use Mockery as m; -use Tests\TestCase; use phpmock\phpunit\PHPMock; use Pterodactyl\Models\Node; use Pterodactyl\Models\Server; use Illuminate\Cache\Repository; -use Illuminate\Contracts\Session\Session; -use Tests\Assertions\ControllerAssertionsTrait; +use Tests\Unit\Http\Controllers\ControllerTestCase; use Pterodactyl\Http\Controllers\Server\Files\DownloadController; -class DownloadControllerTest extends TestCase +class DownloadControllerTest extends ControllerTestCase { - use ControllerAssertionsTrait, PHPMock; + use PHPMock; /** - * @var \Illuminate\Cache\Repository + * @var \Illuminate\Cache\Repository|\Mockery\Mock */ protected $cache; - /** - * @var \Pterodactyl\Http\Controllers\Server\Files\DownloadController - */ - protected $controller; - - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - /** * Setup tests. */ @@ -46,9 +34,6 @@ class DownloadControllerTest extends TestCase 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(); } /** @@ -56,22 +41,33 @@ class DownloadControllerTest extends TestCase */ public function testIndexController() { + $controller = $this->getController(); $server = factory(Server::class)->make(); - $node = factory(Node::class)->make(); - $server->node = $node; + $server->setRelation('node', factory(Node::class)->make()); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('download-files', $server)->once()->andReturnNull(); + $this->setRequestAttribute('server', $server); + + $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(); + $this->cache->shouldReceive('tags')->with(['Server:Downloads'])->once()->andReturnSelf(); + $this->cache->shouldReceive('put')->with('randomString', ['server' => $server->uuid, 'path' => '/my/file.txt'], 5)->once()->andReturnNull(); - $response = $this->controller->index('1234', '/my/file.txt'); + $response = $controller->index($this->request, $server->uuidShort, '/my/file.txt'); $this->assertIsRedirectResponse($response); $this->assertRedirectUrlEquals(sprintf( '%s://%s:%s/v1/server/file/download/%s', $server->node->scheme, $server->node->fqdn, $server->node->daemonListen, 'randomString' ), $response); } + + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\Files\DownloadController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(DownloadController::class, [$this->cache]); + } } diff --git a/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php b/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php index 5a7bfb08d..077cf02d9 100644 --- a/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php +++ b/tests/Unit/Http/Controllers/Server/Files/FileActionsControllerTest.php @@ -10,51 +10,24 @@ namespace Tests\Unit\Http\Controllers\Server\Files; use Mockery as m; -use Tests\TestCase; -use Illuminate\Log\Writer; -use Illuminate\Http\Request; use Pterodactyl\Models\Server; -use Illuminate\Contracts\Session\Session; +use Tests\Traits\MocksRequestException; use GuzzleHttp\Exception\RequestException; -use Pterodactyl\Exceptions\DisplayException; -use Tests\Assertions\ControllerAssertionsTrait; +use Pterodactyl\Exceptions\PterodactylException; +use Tests\Unit\Http\Controllers\ControllerTestCase; use Pterodactyl\Http\Requests\Server\UpdateFileContentsFormRequest; use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; use Pterodactyl\Http\Controllers\Server\Files\FileActionsController; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; -class FileActionsControllerTest extends TestCase +class FileActionsControllerTest extends ControllerTestCase { - use ControllerAssertionsTrait; + use MocksRequestException; /** - * @var \Pterodactyl\Http\Controllers\Server\Files\FileActionsController + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface|\Mockery\Mock */ - 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; + protected $repository; /** * Setup tests. @@ -63,15 +36,7 @@ class FileActionsControllerTest extends TestCase { 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(); + $this->repository = m::mock(FileRepositoryInterface::class); } /** @@ -79,14 +44,16 @@ class FileActionsControllerTest extends TestCase */ public function testIndexController() { + $controller = $this->getController(); $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(); + $this->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); - $response = $this->controller->index($this->request); + $controller->shouldReceive('authorize')->with('list-files', $server)->once()->andReturnNull(); + $this->request->shouldReceive('user->can')->andReturn(true); + + $response = $controller->index($this->request); $this->assertIsViewResponse($response); $this->assertViewNameEquals('server.files.index', $response); } @@ -98,14 +65,16 @@ class FileActionsControllerTest extends TestCase */ public function testCreateController($directory, $expected) { + $controller = $this->getController(); $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->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); + + $controller->shouldReceive('authorize')->with('create-files', $server)->once()->andReturnNull(); $this->request->shouldReceive('get')->with('dir')->andReturn($directory); - $response = $this->controller->create($this->request); + $response = $controller->create($this->request); $this->assertIsViewResponse($response); $this->assertViewNameEquals('server.files.add', $response); $this->assertViewHasKey('directory', $response); @@ -119,20 +88,22 @@ class FileActionsControllerTest extends TestCase */ public function testUpdateController($file, $expected) { + $this->setRequestMockClass(UpdateFileContentsFormRequest::class); + + $controller = $this->getController(); $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() + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('server_token', 'abc123'); + $this->setRequestAttribute('file_stats', 'fileStatsObject'); + $this->mockInjectJavascript(['stat' => 'fileStatsObject']); + + $this->repository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() - ->shouldReceive('setAccessToken')->with($server->daemonSecret)->once()->andReturnSelf() + ->shouldReceive('setAccessToken')->with('abc123')->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); + $response = $controller->update($this->request, '1234', $file); $this->assertIsViewResponse($response); $this->assertViewNameEquals('server.files.edit', $response); $this->assertViewHasKey('file', $response); @@ -140,7 +111,7 @@ class FileActionsControllerTest extends TestCase $this->assertViewHasKey('contents', $response); $this->assertViewHasKey('directory', $response); $this->assertViewKeyEquals('file', $file, $response); - $this->assertViewKeyEquals('stat', ['stats'], $response); + $this->assertViewKeyEquals('stat', 'fileStatsObject', $response); $this->assertViewKeyEquals('contents', 'file contents', $response); $this->assertViewKeyEquals('directory', $expected, $response); } @@ -150,20 +121,23 @@ class FileActionsControllerTest extends TestCase */ public function testExceptionRenderedByUpdateController() { + $this->setRequestMockClass(UpdateFileContentsFormRequest::class); + $this->configureExceptionMock(); + + $controller = $this->getController(); $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); + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('server_token', 'abc123'); + $this->setRequestAttribute('file_stats', 'fileStatsObject'); - $exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnNull(); - $this->writer->shouldReceive('warning')->with($exception)->once()->andReturnNull(); + $this->repository->shouldReceive('setNode')->with($server->node_id)->once()->andThrow($this->getExceptionMock()); 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()); + $controller->update($this->request, '1234', 'file.txt'); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DaemonConnectionException::class, $exception); + $this->assertInstanceOf(RequestException::class, $exception->getPrevious()); } } @@ -199,4 +173,14 @@ class FileActionsControllerTest extends TestCase ['./file.txt', '/'], ]; } + + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\Files\FileActionsController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(FileActionsController::class, [$this->repository]); + } } diff --git a/tests/Unit/Http/Controllers/Server/Files/RemoteRequestControllerTest.php b/tests/Unit/Http/Controllers/Server/Files/RemoteRequestControllerTest.php index 1f818ce83..b44b225e2 100644 --- a/tests/Unit/Http/Controllers/Server/Files/RemoteRequestControllerTest.php +++ b/tests/Unit/Http/Controllers/Server/Files/RemoteRequestControllerTest.php @@ -10,50 +10,29 @@ namespace Tests\Unit\Http\Controllers\Server\Files; use Mockery as m; -use Tests\TestCase; -use Illuminate\Log\Writer; -use Illuminate\Http\Request; use Pterodactyl\Models\Server; -use Illuminate\Contracts\Session\Session; +use Tests\Traits\MocksRequestException; use GuzzleHttp\Exception\RequestException; use Illuminate\Contracts\Config\Repository; -use Tests\Assertions\ControllerAssertionsTrait; +use Pterodactyl\Exceptions\PterodactylException; +use Tests\Unit\Http\Controllers\ControllerTestCase; use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController; -class RemoteRequestControllerTest extends TestCase +class RemoteRequestControllerTest extends ControllerTestCase { - use ControllerAssertionsTrait; + use MocksRequestException; /** - * @var \Illuminate\Contracts\Config\Repository + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock */ protected $config; /** - * @var \Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController + * @var \Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface|\Mockery\Mock */ - protected $controller; - - /** - * @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; + protected $repository; /** * Setup tests. @@ -63,17 +42,7 @@ class RemoteRequestControllerTest extends TestCase parent::setUp(); $this->config = m::mock(Repository::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(RemoteRequestController::class, [ - $this->config, - $this->fileRepository, - $this->session, - $this->writer, - ])->makePartial(); + $this->repository = m::mock(FileRepositoryInterface::class); } /** @@ -81,19 +50,21 @@ class RemoteRequestControllerTest extends TestCase */ public function testDirectoryController() { - $server = factory(Server::class)->make(); + $controller = $this->getController(); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('list-files', $server)->once()->andReturnNull(); + $server = factory(Server::class)->make(); + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('server_token', 'abc123'); + + $controller->shouldReceive('authorize')->with('list-files', $server)->once()->andReturnNull(); $this->request->shouldReceive('input')->with('directory', '/')->once()->andReturn('/'); - $this->session->shouldReceive('get')->with('server_data.token')->once()->andReturn($server->daemonSecret); - $this->fileRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() + $this->repository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() - ->shouldReceive('setAccessToken')->with($server->daemonSecret)->once()->andReturnSelf() + ->shouldReceive('setAccessToken')->with('abc123')->once()->andReturnSelf() ->shouldReceive('getDirectory')->with('/')->once()->andReturn(['folders' => 1, 'files' => 2]); $this->config->shouldReceive('get')->with('pterodactyl.files.editable')->once()->andReturn([]); - $response = $this->controller->directory($this->request); + $response = $controller->directory($this->request); $this->assertIsViewResponse($response); $this->assertViewNameEquals('server.files.list', $response); $this->assertViewHasKey('files', $response); @@ -112,21 +83,22 @@ class RemoteRequestControllerTest extends TestCase */ public function testExceptionThrownByDaemonConnectionIsHandledByDisplayController() { + $this->configureExceptionMock(); + $controller = $this->getController(); + $server = factory(Server::class)->make(); - $exception = m::mock(RequestException::class); + $this->setRequestAttribute('server', $server); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('list-files', $server)->once()->andReturnNull(); + $controller->shouldReceive('authorize')->with('list-files', $server)->once()->andReturnNull(); $this->request->shouldReceive('input')->with('directory', '/')->once()->andReturn('/'); - $this->fileRepository->shouldReceive('setNode')->with($server->node_id)->once()->andThrow($exception); + $this->repository->shouldReceive('setNode')->with($server->node_id)->once()->andThrow($this->getExceptionMock()); - $this->writer->shouldReceive('warning')->with($exception)->once()->andReturnNull(); - $exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnNull(); - - $response = $this->controller->directory($this->request); - $this->assertIsJsonResponse($response); - $this->assertResponseJsonEquals(['error' => trans('exceptions.daemon_connection_failed', ['code' => 'E_CONN_REFUSED'])], $response); - $this->assertResponseCodeEquals(500, $response); + try { + $controller->directory($this->request); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DaemonConnectionException::class, $exception); + $this->assertInstanceOf(RequestException::class, $exception->getPrevious()); + } } /** @@ -134,19 +106,21 @@ class RemoteRequestControllerTest extends TestCase */ public function testStoreController() { - $server = factory(Server::class)->make(); + $controller = $this->getController(); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('save-files', $server)->once()->andReturnNull(); - $this->session->shouldReceive('get')->with('server_data.token')->once()->andReturn($server->daemonSecret); + $server = factory(Server::class)->make(); + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('server_token', 'abc123'); + + $controller->shouldReceive('authorize')->with('save-files', $server)->once()->andReturnNull(); $this->request->shouldReceive('input')->with('file')->once()->andReturn('file.txt'); $this->request->shouldReceive('input')->with('contents')->once()->andReturn('file contents'); - $this->fileRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() + $this->repository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() - ->shouldReceive('setAccessToken')->with($server->daemonSecret)->once()->andReturnSelf() + ->shouldReceive('setAccessToken')->with('abc123')->once()->andReturnSelf() ->shouldReceive('putContent')->with('file.txt', 'file contents')->once()->andReturnNull(); - $response = $this->controller->store($this->request, '1234'); + $response = $controller->store($this->request); $this->assertIsResponse($response); $this->assertResponseCodeEquals(204, $response); } @@ -156,19 +130,30 @@ class RemoteRequestControllerTest extends TestCase */ public function testExceptionThrownByDaemonConnectionIsHandledByStoreController() { + $this->configureExceptionMock(); + $controller = $this->getController(); + $server = factory(Server::class)->make(); - $exception = m::mock(RequestException::class); + $this->setRequestAttribute('server', $server); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('save-files', $server)->once()->andReturnNull(); - $this->fileRepository->shouldReceive('setNode')->with($server->node_id)->once()->andThrow($exception); + $controller->shouldReceive('authorize')->with('save-files', $server)->once()->andReturnNull(); + $this->repository->shouldReceive('setNode')->with($server->node_id)->once()->andThrow($this->getExceptionMock()); - $this->writer->shouldReceive('warning')->with($exception)->once()->andReturnNull(); - $exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnNull(); + try { + $controller->store($this->request); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DaemonConnectionException::class, $exception); + $this->assertInstanceOf(RequestException::class, $exception->getPrevious()); + } + } - $response = $this->controller->store($this->request, '1234'); - $this->assertIsJsonResponse($response); - $this->assertResponseJsonEquals(['error' => trans('exceptions.daemon_connection_failed', ['code' => 'E_CONN_REFUSED'])], $response); - $this->assertResponseCodeEquals(500, $response); + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(RemoteRequestController::class, [$this->config, $this->repository]); } } diff --git a/tests/Unit/Http/Controllers/Server/SubuserControllerTest.php b/tests/Unit/Http/Controllers/Server/SubuserControllerTest.php index 86d7bf29a..e243cf799 100644 --- a/tests/Unit/Http/Controllers/Server/SubuserControllerTest.php +++ b/tests/Unit/Http/Controllers/Server/SubuserControllerTest.php @@ -10,61 +10,43 @@ namespace Tests\Unit\Http\Controllers\Server; use Mockery as m; -use Tests\TestCase; -use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Permission; use Prologue\Alerts\AlertsMessageBag; -use Illuminate\Contracts\Session\Session; -use Tests\Assertions\ControllerAssertionsTrait; +use Tests\Unit\Http\Controllers\ControllerTestCase; use Pterodactyl\Services\Subusers\SubuserUpdateService; use Pterodactyl\Services\Subusers\SubuserCreationService; use Pterodactyl\Services\Subusers\SubuserDeletionService; use Pterodactyl\Http\Controllers\Server\SubuserController; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; +use Pterodactyl\Http\Requests\Server\Subuser\SubuserStoreFormRequest; +use Pterodactyl\Http\Requests\Server\Subuser\SubuserUpdateFormRequest; -class SubuserControllerTest extends TestCase +class SubuserControllerTest extends ControllerTestCase { - use ControllerAssertionsTrait; - /** - * @var \Prologue\Alerts\AlertsMessageBag + * @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock */ protected $alert; /** - * @var \Pterodactyl\Http\Controllers\Server\SubuserController - */ - protected $controller; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface|\Mockery\Mock */ protected $repository; /** - * @var \Illuminate\Http\Request - */ - protected $request; - - /** - * @var \Illuminate\Contracts\Session\Session - */ - protected $session; - - /** - * @var \Pterodactyl\Services\Subusers\SubuserCreationService + * @var \Pterodactyl\Services\Subusers\SubuserCreationService|\Mockery\Mock */ protected $subuserCreationService; /** - * @var \Pterodactyl\Services\Subusers\SubuserDeletionService + * @var \Pterodactyl\Services\Subusers\SubuserDeletionService|\Mockery\Mock */ protected $subuserDeletionService; /** - * @var \Pterodactyl\Services\Subusers\SubuserUpdateService + * @var \Pterodactyl\Services\Subusers\SubuserUpdateService|\Mockery\Mock */ protected $subuserUpdateService; @@ -77,20 +59,9 @@ class SubuserControllerTest extends TestCase $this->alert = m::mock(AlertsMessageBag::class); $this->repository = m::mock(SubuserRepositoryInterface::class); - $this->request = m::mock(Request::class); - $this->session = m::mock(Session::class); $this->subuserCreationService = m::mock(SubuserCreationService::class); $this->subuserDeletionService = m::mock(SubuserDeletionService::class); $this->subuserUpdateService = m::mock(SubuserUpdateService::class); - - $this->controller = m::mock(SubuserController::class, [ - $this->alert, - $this->session, - $this->subuserCreationService, - $this->subuserDeletionService, - $this->repository, - $this->subuserUpdateService, - ])->makePartial(); } /* @@ -98,14 +69,16 @@ class SubuserControllerTest extends TestCase */ public function testIndexController() { + $controller = $this->getController(); $server = factory(Server::class)->make(); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('list-subusers', $server)->once()->andReturnNull(); - $this->controller->shouldReceive('injectJavascript')->withNoArgs()->once()->andReturnNull(); + $this->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); + + $controller->shouldReceive('authorize')->with('list-subusers', $server)->once()->andReturnNull(); $this->repository->shouldReceive('findWhere')->with([['server_id', '=', $server->id]])->once()->andReturn([]); - $response = $this->controller->index(); + $response = $controller->index($this->request); $this->assertIsViewResponse($response); $this->assertViewNameEquals('server.users.index', $response); $this->assertViewHasKey('subusers', $response); @@ -116,20 +89,22 @@ class SubuserControllerTest extends TestCase */ public function testViewController() { - $subuser = factory(Subuser::class)->make([ - 'permissions' => collect([ - (object) ['permission' => 'some.permission'], - (object) ['permission' => 'another.permission'], - ]), - ]); + $controller = $this->getController(); + $subuser = factory(Subuser::class)->make(); + $subuser->setRelation('permissions', collect([ + (object) ['permission' => 'some.permission'], + (object) ['permission' => 'another.permission'], + ])); $server = factory(Server::class)->make(); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('view-subuser', $server)->once()->andReturnNull(); - $this->repository->shouldReceive('getWithPermissions')->with(1234)->once()->andReturn($subuser); - $this->controller->shouldReceive('injectJavascript')->withNoArgs()->once()->andReturnNull(); + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('subuser', $subuser); + $this->mockInjectJavascript(); - $response = $this->controller->view($server->uuid, 1234); + $controller->shouldReceive('authorize')->with('view-subuser', $server)->once()->andReturnNull(); + $this->repository->shouldReceive('getWithPermissions')->with($subuser)->once()->andReturn($subuser); + + $response = $controller->view($this->request); $this->assertIsViewResponse($response); $this->assertViewNameEquals('server.users.view', $response); $this->assertViewHasKey('subuser', $response); @@ -148,18 +123,21 @@ class SubuserControllerTest extends TestCase */ public function testUpdateController() { - $server = factory(Server::class)->make(); + $this->setRequestMockClass(SubuserUpdateFormRequest::class); + + $controller = $this->getController(); + $subuser = factory(Subuser::class)->make(); + + $this->setRequestAttribute('subuser', $subuser); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('edit-subuser', $server)->once()->andReturnNull(); $this->request->shouldReceive('input')->with('permissions', [])->once()->andReturn(['some.permission']); - $this->subuserUpdateService->shouldReceive('handle')->with(1234, ['some.permission'])->once()->andReturnNull(); - $this->alert->shouldReceive('success')->with(trans('server.users.user_updated'))->once()->andReturnSelf() - ->shouldReceive('flash')->withNoArgs()->once()->andReturnNull(); + $this->subuserUpdateService->shouldReceive('handle')->with($subuser, ['some.permission'])->once()->andReturnNull(); + $this->alert->shouldReceive('success')->with(trans('server.users.user_updated'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull(); - $response = $this->controller->update($this->request, $server->uuid, 1234); + $response = $controller->update($this->request, 'abcd1234', 1234); $this->assertIsRedirectResponse($response); - $this->assertRedirectRouteEquals('server.subusers.view', $response, ['uuid' => $server->uuid, 'id' => 1234]); + $this->assertRedirectRouteEquals('server.subusers.view', $response, ['uuid' => 'abcd1234', 'id' => 1234]); } /** @@ -167,13 +145,15 @@ class SubuserControllerTest extends TestCase */ public function testCreateController() { + $controller = $this->getController(); $server = factory(Server::class)->make(); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('create-subuser', $server)->once()->andReturnNull(); - $this->controller->shouldReceive('injectJavascript')->withNoArgs()->once()->andReturnNull(); + $this->setRequestAttribute('server', $server); + $this->mockInjectJavascript(); - $response = $this->controller->create(); + $controller->shouldReceive('authorize')->with('create-subuser', $server)->once()->andReturnNull(); + + $response = $controller->create($this->request); $this->assertIsViewResponse($response); $this->assertViewNameEquals('server.users.new', $response); $this->assertViewHasKey('permissions', $response); @@ -185,20 +165,26 @@ class SubuserControllerTest extends TestCase */ public function testStoreController() { + $this->setRequestMockClass(SubuserStoreFormRequest::class); + $controller = $this->getController(); + $server = factory(Server::class)->make(); $subuser = factory(Subuser::class)->make(); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('create-subuser', $server)->once()->andReturnNull(); + $this->setRequestAttribute('server', $server); + $this->request->shouldReceive('input')->with('email')->once()->andReturn('user@test.com'); $this->request->shouldReceive('input')->with('permissions', [])->once()->andReturn(['some.permission']); $this->subuserCreationService->shouldReceive('handle')->with($server, 'user@test.com', ['some.permission'])->once()->andReturn($subuser); - $this->alert->shouldReceive('success')->with(trans('server.users.user_assigned'))->once()->andReturnSelf() - ->shouldReceive('flash')->withNoArgs()->once()->andReturnNull(); + $this->alert->shouldReceive('success')->with(trans('server.users.user_assigned'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull(); - $response = $this->controller->store($this->request, $server->uuid); + $response = $controller->store($this->request); $this->assertIsRedirectResponse($response); - $this->assertRedirectRouteEquals('server.subusers.view', $response, ['uuid' => $server->uuid, 'id' => $subuser->id]); + $this->assertRedirectRouteEquals('server.subusers.view', $response, [ + 'uuid' => $server->uuid, + 'id' => $subuser->id, + ]); } /** @@ -206,14 +192,35 @@ class SubuserControllerTest extends TestCase */ public function testDeleteController() { + $controller = $this->getController(); + $server = factory(Server::class)->make(); + $subuser = factory(Subuser::class)->make(); - $this->session->shouldReceive('get')->with('server_data.model')->once()->andReturn($server); - $this->controller->shouldReceive('authorize')->with('delete-subuser', $server)->once()->andReturnNull(); - $this->subuserDeletionService->shouldReceive('handle')->with(1234)->once()->andReturnNull(); + $this->setRequestAttribute('server', $server); + $this->setRequestAttribute('subuser', $subuser); - $response = $this->controller->delete($server->uuid, 1234); + $controller->shouldReceive('authorize')->with('delete-subuser', $server)->once()->andReturnNull(); + $this->subuserDeletionService->shouldReceive('handle')->with($subuser)->once()->andReturnNull(); + + $response = $controller->delete($this->request); $this->assertIsResponse($response); $this->assertResponseCodeEquals(204, $response); } + + /** + * Return a mocked instance of the controller to allow access to authorization functionality. + * + * @return \Pterodactyl\Http\Controllers\Server\SubuserController|\Mockery\Mock + */ + private function getController() + { + return $this->buildMockedController(SubuserController::class, [ + $this->alert, + $this->subuserCreationService, + $this->subuserDeletionService, + $this->repository, + $this->subuserUpdateService, + ]); + } } diff --git a/tests/Unit/Http/Middleware/MiddlewareTestCase.php b/tests/Unit/Http/Middleware/MiddlewareTestCase.php index 463570f0e..6356cde20 100644 --- a/tests/Unit/Http/Middleware/MiddlewareTestCase.php +++ b/tests/Unit/Http/Middleware/MiddlewareTestCase.php @@ -2,22 +2,14 @@ namespace Tests\Unit\Http\Middleware; -use Mockery as m; use Tests\TestCase; -use Illuminate\Http\Request; -use Pterodactyl\Models\User; +use Tests\Traits\Http\RequestMockHelpers; use Tests\Traits\Http\MocksMiddlewareClosure; -use Symfony\Component\HttpFoundation\ParameterBag; use Tests\Assertions\MiddlewareAttributeAssertionsTrait; abstract class MiddlewareTestCase extends TestCase { - use MiddlewareAttributeAssertionsTrait, MocksMiddlewareClosure; - - /** - * @var \Illuminate\Http\Request|\Mockery\Mock - */ - protected $request; + use MiddlewareAttributeAssertionsTrait, MocksMiddlewareClosure, RequestMockHelpers; /** * Setup tests with a mocked request object and normal attributes. @@ -26,33 +18,6 @@ abstract class MiddlewareTestCase extends TestCase { parent::setUp(); - $this->request = m::mock(Request::class); - $this->request->attributes = new ParameterBag(); - } - - /** - * Set a request attribute on the mock object. - * - * @param string $attribute - * @param mixed $value - */ - protected function setRequestAttribute(string $attribute, $value) - { - $this->request->attributes->set($attribute, $value); - } - - /** - * Sets the mocked request user. If a user model is not provided, a factory model - * will be created and returned. - * - * @param \Pterodactyl\Models\User|null $user - * @return \Pterodactyl\Models\User - */ - protected function setRequestUser(User $user = null): User - { - $user = $user instanceof User ? $user : factory(User::class)->make(); - $this->request->shouldReceive('user')->withNoArgs()->andReturn($user); - - return $user; + $this->buildRequestMock(); } } From 133fd17da6975e364acc931149b51fb696903ffd Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 3 Nov 2017 16:52:18 -0500 Subject: [PATCH 07/10] Fix subuser unit tests --- .../Subusers/SubuserDeletionService.php | 6 +- .../Subusers/SubuserDeletionServiceTest.php | 38 ++----- .../Subusers/SubuserUpdateServiceTest.php | 102 ++++++++---------- 3 files changed, 57 insertions(+), 89 deletions(-) diff --git a/app/Services/Subusers/SubuserDeletionService.php b/app/Services/Subusers/SubuserDeletionService.php index f754a5221..6b100cd18 100644 --- a/app/Services/Subusers/SubuserDeletionService.php +++ b/app/Services/Subusers/SubuserDeletionService.php @@ -19,17 +19,17 @@ class SubuserDeletionService /** * @var \Illuminate\Database\ConnectionInterface */ - protected $connection; + private $connection; /** * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService */ - protected $keyDeletionService; + private $keyDeletionService; /** * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface */ - protected $repository; + private $repository; /** * SubuserDeletionService constructor. diff --git a/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php b/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php index c1ba27b0b..ed8c5fab1 100644 --- a/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php +++ b/tests/Unit/Services/Subusers/SubuserDeletionServiceTest.php @@ -22,22 +22,17 @@ class SubuserDeletionServiceTest extends TestCase /** * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock */ - protected $connection; + private $connection; /** * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService|\Mockery\Mock */ - protected $keyDeletionService; + private $keyDeletionService; /** * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface|\Mockery\Mock */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Subusers\SubuserDeletionService - */ - protected $service; + private $repository; /** * Setup tests. @@ -49,18 +44,12 @@ class SubuserDeletionServiceTest extends TestCase $this->connection = m::mock(ConnectionInterface::class); $this->keyDeletionService = m::mock(DaemonKeyDeletionService::class); $this->repository = m::mock(SubuserRepositoryInterface::class); - - $this->service = new SubuserDeletionService( - $this->connection, - $this->keyDeletionService, - $this->repository - ); } /** * Test that a subuser is deleted correctly. */ - public function testSubuserIsDeletedIfModelIsPassed() + public function testSubuserIsDeleted() { $subuser = factory(Subuser::class)->make(); @@ -69,24 +58,17 @@ class SubuserDeletionServiceTest extends TestCase $this->repository->shouldReceive('delete')->with($subuser->id)->once()->andReturnNull(); $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - $this->service->handle($subuser); + $this->getService()->handle($subuser); $this->assertTrue(true); } /** - * Test that a subuser is deleted correctly if only the subuser ID is passed. + * Return an instance of the service with mocked dependencies for testing. + * + * @return \Pterodactyl\Services\Subusers\SubuserDeletionService */ - public function testSubuserIsDeletedIfIdPassedInPlaceOfModel() + private function getService(): SubuserDeletionService { - $subuser = factory(Subuser::class)->make(); - - $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser); - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->keyDeletionService->shouldReceive('handle')->with($subuser->server_id, $subuser->user_id)->once()->andReturnNull(); - $this->repository->shouldReceive('delete')->with($subuser->id)->once()->andReturnNull(); - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $this->service->handle($subuser->id); - $this->assertTrue(true); + return new SubuserDeletionService($this->connection, $this->keyDeletionService, $this->repository); } } diff --git a/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php b/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php index b4a458275..7a6aaf454 100644 --- a/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php +++ b/tests/Unit/Services/Subusers/SubuserUpdateServiceTest.php @@ -11,36 +11,33 @@ namespace Tests\Unit\Services\Subusers; use Mockery as m; use Tests\TestCase; -use Illuminate\Log\Writer; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; +use Tests\Traits\MocksRequestException; use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\PterodactylException; use Pterodactyl\Services\Subusers\SubuserUpdateService; use Pterodactyl\Services\Subusers\PermissionCreationService; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class SubuserUpdateServiceTest extends TestCase { + use MocksRequestException; + /** * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock */ - protected $connection; + private $connection; /** * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface|\Mockery\Mock */ - protected $daemonRepository; - - /** - * @var \GuzzleHttp\Exception\RequestException|\Mockery\Mock - */ - protected $exception; + private $daemonRepository; /** * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService|\Mockery\Mock @@ -50,27 +47,17 @@ class SubuserUpdateServiceTest extends TestCase /** * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface|\Mockery\Mock */ - protected $permissionRepository; + private $permissionRepository; /** * @var \Pterodactyl\Services\Subusers\PermissionCreationService|\Mockery\Mock */ - protected $permissionService; + private $permissionService; /** * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface|\Mockery\Mock */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Subusers\SubuserUpdateService - */ - protected $service; - - /** - * @var \Illuminate\Log\Writer|\Mockery\Mock - */ - protected $writer; + private $repository; /** * Setup tests. @@ -81,22 +68,10 @@ class SubuserUpdateServiceTest extends TestCase $this->connection = m::mock(ConnectionInterface::class); $this->daemonRepository = m::mock(DaemonServerRepositoryInterface::class); - $this->exception = m::mock(RequestException::class); $this->keyProviderService = m::mock(DaemonKeyProviderService::class); $this->permissionRepository = m::mock(PermissionRepositoryInterface::class); $this->permissionService = m::mock(PermissionCreationService::class); $this->repository = m::mock(SubuserRepositoryInterface::class); - $this->writer = m::mock(Writer::class); - - $this->service = new SubuserUpdateService( - $this->connection, - $this->keyProviderService, - $this->daemonRepository, - $this->permissionService, - $this->permissionRepository, - $this->repository, - $this->writer - ); } /** @@ -105,22 +80,20 @@ class SubuserUpdateServiceTest extends TestCase public function testPermissionsAreUpdated() { $subuser = factory(Subuser::class)->make(); - $subuser->server = factory(Server::class)->make(); + $subuser->setRelation('server', factory(Server::class)->make()); - $this->repository->shouldReceive('getWithServer')->with($subuser->id)->once()->andReturn($subuser); + $this->repository->shouldReceive('getWithServer')->with($subuser)->once()->andReturn($subuser); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]]) - ->once()->andReturnNull(); + $this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]])->once()->andReturnNull(); $this->permissionService->shouldReceive('handle')->with($subuser->id, ['some-permission'])->once()->andReturnNull(); - $this->keyProviderService->shouldReceive('handle')->with($subuser->server_id, $subuser->user_id, false) - ->once()->andReturn('test123'); - $this->daemonRepository->shouldReceive('setNode')->with($subuser->server->node_id)->once()->andReturnSelf() - ->shouldReceive('revokeAccessKey')->with('test123')->once()->andReturnNull(); + $this->keyProviderService->shouldReceive('handle')->with($subuser->server_id, $subuser->user_id, false)->once()->andReturn('test123'); + $this->daemonRepository->shouldReceive('setNode')->with($subuser->server->node_id)->once()->andReturnSelf(); + $this->daemonRepository->shouldReceive('revokeAccessKey')->with('test123')->once()->andReturnNull(); $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - $this->service->handle($subuser->id, ['some-permission']); + $this->getService()->handle($subuser, ['some-permission']); $this->assertTrue(true); } @@ -129,29 +102,42 @@ class SubuserUpdateServiceTest extends TestCase */ public function testExceptionIsThrownIfDaemonConnectionFails() { - $subuser = factory(Subuser::class)->make(); - $subuser->server = factory(Server::class)->make(); + $this->configureExceptionMock(); - $this->repository->shouldReceive('getWithServer')->with($subuser->id)->once()->andReturn($subuser); + $subuser = factory(Subuser::class)->make(); + $subuser->setRelation('server', factory(Server::class)->make()); + + $this->repository->shouldReceive('getWithServer')->with($subuser)->once()->andReturn($subuser); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]]) - ->once()->andReturnNull(); + $this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]])->once()->andReturnNull(); $this->permissionService->shouldReceive('handle')->with($subuser->id, [])->once()->andReturnNull(); - $this->keyProviderService->shouldReceive('handle')->with($subuser->server_id, $subuser->user_id, false) - ->once()->andReturn('test123'); - $this->daemonRepository->shouldReceive('setNode')->once()->andThrow($this->exception); + $this->keyProviderService->shouldReceive('handle')->with($subuser->server_id, $subuser->user_id, false)->once()->andReturn('test123'); + $this->daemonRepository->shouldReceive('setNode')->with($subuser->server->node_id)->once()->andThrow($this->getExceptionMock()); $this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); - $this->writer->shouldReceive('warning')->with($this->exception)->once()->andReturnNull(); - $this->exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnNull(); try { - $this->service->handle($subuser->id, []); + $this->getService()->handle($subuser, []); } catch (PterodactylException $exception) { - $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals(trans('exceptions.daemon_connection_failed', [ - 'code' => 'E_CONN_REFUSED', - ]), $exception->getMessage()); + $this->assertInstanceOf(DaemonConnectionException::class, $exception); + $this->assertInstanceOf(RequestException::class, $exception->getPrevious()); } } + + /** + * Return an instance of the service with mocked dependencies for testing. + * + * @return \Pterodactyl\Services\Subusers\SubuserUpdateService + */ + private function getService(): SubuserUpdateService + { + return new SubuserUpdateService( + $this->connection, + $this->keyProviderService, + $this->daemonRepository, + $this->permissionService, + $this->permissionRepository, + $this->repository + ); + } } From 7882250bafd8840ceb95fec8171216b7f04d0247 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 3 Nov 2017 18:16:49 -0500 Subject: [PATCH 08/10] Add more middleware tests --- app/Http/Middleware/Authenticate.php | 29 +-- app/Http/Middleware/DaemonAuthenticate.php | 13 +- app/Http/Middleware/LanguageMiddleware.php | 13 +- .../Middleware/RedirectIfAuthenticated.php | 4 +- .../RequireTwoFactorAuthentication.php | 8 +- database/factories/ModelFactory.php | 1 + tests/Traits/Http/MocksMiddlewareClosure.php | 5 - .../Http/Middleware/AdminAuthenticateTest.php | 62 ++++++ .../Unit/Http/Middleware/AuthenticateTest.php | 57 ++++++ .../Middleware/DaemonAuthenticateTest.php | 77 ++++++++ .../Middleware/LanguageMiddlewareTest.php | 53 +++++ .../RedirectIfAuthenticatedTest.php | 60 ++++++ .../RequireTwoFactorAuthenticationTest.php | 181 ++++++++++++++++++ 13 files changed, 515 insertions(+), 48 deletions(-) create mode 100644 tests/Unit/Http/Middleware/AdminAuthenticateTest.php create mode 100644 tests/Unit/Http/Middleware/AuthenticateTest.php create mode 100644 tests/Unit/Http/Middleware/DaemonAuthenticateTest.php create mode 100644 tests/Unit/Http/Middleware/LanguageMiddlewareTest.php create mode 100644 tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php create mode 100644 tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 019f92a2f..473ad5064 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -4,41 +4,26 @@ namespace Pterodactyl\Http\Middleware; use Closure; use Illuminate\Http\Request; -use Illuminate\Contracts\Auth\Guard; +use Illuminate\Auth\AuthenticationException; class Authenticate { - /** - * The Guard implementation. - * - * @var \Illuminate\Contracts\Auth\Guard - */ - protected $auth; - - /** - * Create a new filter instance. - * - * @param \Illuminate\Contracts\Auth\Guard $auth - */ - public function __construct(Guard $auth) - { - $this->auth = $auth; - } - /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed + * + * @throws \Illuminate\Auth\AuthenticationException */ public function handle(Request $request, Closure $next) { - if ($this->auth->guest()) { - if ($request->ajax()) { - return response('Unauthorized.', 401); + if (! $request->user()) { + if ($request->ajax() || $request->expectsJson()) { + throw new AuthenticationException(); } else { - return redirect()->guest('auth/login'); + return redirect()->route('auth.login'); } } diff --git a/app/Http/Middleware/DaemonAuthenticate.php b/app/Http/Middleware/DaemonAuthenticate.php index ca77e70d2..775a7783c 100644 --- a/app/Http/Middleware/DaemonAuthenticate.php +++ b/app/Http/Middleware/DaemonAuthenticate.php @@ -11,10 +11,8 @@ namespace Pterodactyl\Http\Middleware; use Closure; use Illuminate\Http\Request; -use Pterodactyl\Models\Node; -use Symfony\Component\HttpKernel\Exception\HttpException; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class DaemonAuthenticate { @@ -56,15 +54,10 @@ class DaemonAuthenticate } if (! $request->header('X-Access-Node')) { - throw new HttpException(403); - } - - try { - $node = $this->repository->findWhere(['daemonSecret' => $request->header('X-Access-Node')]); - } catch (RecordNotFoundException $exception) { - throw new HttpException(401); + throw new AccessDeniedHttpException; } + $node = $this->repository->findWhere(['daemonSecret' => $request->header('X-Access-Node')]); $request->attributes->set('node', $node); return $next($request); diff --git a/app/Http/Middleware/LanguageMiddleware.php b/app/Http/Middleware/LanguageMiddleware.php index d348f3b8d..2e581f58f 100644 --- a/app/Http/Middleware/LanguageMiddleware.php +++ b/app/Http/Middleware/LanguageMiddleware.php @@ -11,11 +11,16 @@ namespace Pterodactyl\Http\Middleware; use Closure; use Illuminate\Http\Request; -use Illuminate\Support\Facades\App; +use Illuminate\Foundation\Application; use Illuminate\Contracts\Config\Repository; class LanguageMiddleware { + /** + * @var \Illuminate\Foundation\Application + */ + private $app; + /** * @var \Illuminate\Contracts\Config\Repository */ @@ -24,10 +29,12 @@ class LanguageMiddleware /** * LanguageMiddleware constructor. * + * @param \Illuminate\Foundation\Application $app * @param \Illuminate\Contracts\Config\Repository $config */ - public function __construct(Repository $config) + public function __construct(Application $app, Repository $config) { + $this->app = $app; $this->config = $config; } @@ -40,7 +47,7 @@ class LanguageMiddleware */ public function handle(Request $request, Closure $next) { - App::setLocale($this->config->get('app.locale', 'en')); + $this->app->setLocale($this->config->get('app.locale', 'en')); return $next($request); } diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index ae55fef92..8a5220cb5 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -9,7 +9,7 @@ use Illuminate\Auth\AuthManager; class RedirectIfAuthenticated { /** - * @var \Illuminate\Contracts\Auth\Guard + * @var \Illuminate\Auth\AuthManager */ private $authManager; @@ -34,7 +34,7 @@ class RedirectIfAuthenticated public function handle(Request $request, Closure $next, string $guard = null) { if ($this->authManager->guard($guard)->check()) { - return redirect(route('index')); + return redirect()->route('index'); } return $next($request); diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index 75fb01664..bc5ff70ee 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -73,27 +73,23 @@ class RequireTwoFactorAuthentication */ public function handle(Request $request, Closure $next) { - // Ignore non-users if (! $request->user()) { return $next($request); } - // Skip the 2FA pages if (in_array($request->route()->getName(), $this->except)) { return $next($request); } - // Get the setting switch ((int) $this->settings->get('2fa', 0)) { case self::LEVEL_NONE: return $next($request); - + break; case self::LEVEL_ADMIN: - if (! $request->user()->root_admin) { + if (! $request->user()->root_admin || $request->user()->use_totp) { return $next($request); } break; - case self::LEVEL_ALL: if ($request->user()->use_totp) { return $next($request); diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 54ba984be..1df550e0e 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -74,6 +74,7 @@ $factory->define(Pterodactyl\Models\Location::class, function (Faker\Generator $ $factory->define(Pterodactyl\Models\Node::class, function (Faker\Generator $faker) { return [ 'id' => $faker->unique()->randomNumber(), + 'uuid' => $faker->unique()->uuid, 'public' => true, 'name' => $faker->firstName, 'fqdn' => $faker->ipv4, diff --git a/tests/Traits/Http/MocksMiddlewareClosure.php b/tests/Traits/Http/MocksMiddlewareClosure.php index 53b922585..5c34c4fb6 100644 --- a/tests/Traits/Http/MocksMiddlewareClosure.php +++ b/tests/Traits/Http/MocksMiddlewareClosure.php @@ -8,11 +8,6 @@ use BadFunctionCallException; trait MocksMiddlewareClosure { - /** - * @var \Illuminate\Http\Request - */ - protected $request; - /** * Provide a closure to be used when validating that the response from the middleware * is the same request object we passed into it. diff --git a/tests/Unit/Http/Middleware/AdminAuthenticateTest.php b/tests/Unit/Http/Middleware/AdminAuthenticateTest.php new file mode 100644 index 000000000..2e1850505 --- /dev/null +++ b/tests/Unit/Http/Middleware/AdminAuthenticateTest.php @@ -0,0 +1,62 @@ +make(['root_admin' => 1]); + + $this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that a missing user in the request triggers an error. + */ + public function testExceptionIsThrownIfUserDoesNotExist() + { + $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); + + try { + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } catch (HttpException $exception) { + $this->assertEquals(403, $exception->getStatusCode()); + } + } + + /** + * Test that an exception is thrown if the user is not an admin. + */ + public function testExceptionIsThrownIfUserIsNotAnAdmin() + { + $user = factory(User::class)->make(['root_admin' => 0]); + + $this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user); + + try { + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } catch (HttpException $exception) { + $this->assertEquals(403, $exception->getStatusCode()); + } + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\AdminAuthenticate + */ + private function getMiddleware(): AdminAuthenticate + { + return new AdminAuthenticate(); + } +} diff --git a/tests/Unit/Http/Middleware/AuthenticateTest.php b/tests/Unit/Http/Middleware/AuthenticateTest.php new file mode 100644 index 000000000..88c5990d5 --- /dev/null +++ b/tests/Unit/Http/Middleware/AuthenticateTest.php @@ -0,0 +1,57 @@ +request->shouldReceive('user')->withNoArgs()->once()->andReturn(true); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that a logged out user results in a redirect. + */ + public function testLoggedOutUser() + { + $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); + $this->request->shouldReceive('ajax')->withNoArgs()->once()->andReturn(false); + $this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(false); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertEquals(302, $response->getStatusCode()); + $this->assertEquals(route('auth.login'), $response->getTargetUrl()); + } + + /** + * Test that a logged out user via an API/Ajax request returns a HTTP error. + * + * @expectedException \Illuminate\Auth\AuthenticationException + */ + public function testLoggedOUtUserApiRequest() + { + $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); + $this->request->shouldReceive('ajax')->withNoArgs()->once()->andReturn(true); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\Authenticate + */ + private function getMiddleware(): Authenticate + { + return new Authenticate(); + } +} diff --git a/tests/Unit/Http/Middleware/DaemonAuthenticateTest.php b/tests/Unit/Http/Middleware/DaemonAuthenticateTest.php new file mode 100644 index 000000000..f6531bfbc --- /dev/null +++ b/tests/Unit/Http/Middleware/DaemonAuthenticateTest.php @@ -0,0 +1,77 @@ +repository = m::mock(NodeRepositoryInterface::class); + } + + /** + * Test a valid daemon connection. + */ + public function testValidDaemonConnection() + { + $node = factory(Node::class)->make(); + + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.name'); + $this->request->shouldReceive('header')->with('X-Access-Node')->twice()->andReturn($node->uuid); + + $this->repository->shouldReceive('findWhere')->with(['daemonSecret' => $node->uuid])->once()->andReturn($node); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestHasAttribute('node'); + $this->assertRequestAttributeEquals($node, 'node'); + } + + /** + * Test that ignored routes do not continue through the middleware. + */ + public function testIgnoredRouteShouldContinue() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('daemon.configuration'); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertRequestMissingAttribute('node'); + } + + /** + * Test that a request missing a X-Access-Node header causes an exception. + * + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testExceptionThrownIfMissingHeader() + { + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.name'); + $this->request->shouldReceive('header')->with('X-Access-Node')->once()->andReturn(false); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\DaemonAuthenticate + */ + private function getMiddleware(): DaemonAuthenticate + { + return new DaemonAuthenticate($this->repository); + } +} diff --git a/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php b/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php new file mode 100644 index 000000000..c156665aa --- /dev/null +++ b/tests/Unit/Http/Middleware/LanguageMiddlewareTest.php @@ -0,0 +1,53 @@ +appMock = m::mock(Application::class); + $this->config = m::mock(Repository::class); + } + + /** + * Test that a language is defined via the middleware. + */ + public function testLanguageIsSet() + { + $this->config->shouldReceive('get')->with('app.locale', 'en')->once()->andReturn('en'); + $this->appMock->shouldReceive('setLocale')->with('en')->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\LanguageMiddleware + */ + private function getMiddleware(): LanguageMiddleware + { + return new LanguageMiddleware($this->appMock, $this->config); + } +} diff --git a/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php new file mode 100644 index 000000000..ee56156d9 --- /dev/null +++ b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php @@ -0,0 +1,60 @@ +authManager = m::mock(AuthManager::class); + } + + /** + * Test that an authenticated user is redirected. + */ + public function testAuthenticatedUserIsRedirected() + { + $this->authManager->shouldReceive('guard')->with(null)->once()->andReturnSelf(); + $this->authManager->shouldReceive('check')->with(null)->once()->andReturn(true); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertEquals(route('index'), $response->getTargetUrl()); + } + + /** + * Test that a non-authenticated user continues through the middleware. + */ + public function testNonAuthenticatedUserIsNotRedirected() + { + $this->authManager->shouldReceive('guard')->with(null)->once()->andReturnSelf(); + $this->authManager->shouldReceive('check')->with(null)->once()->andReturn(false); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\RedirectIfAuthenticated + */ + private function getMiddleware(): RedirectIfAuthenticated + { + return new RedirectIfAuthenticated($this->authManager); + } +} diff --git a/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php b/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php new file mode 100644 index 000000000..fac13aab9 --- /dev/null +++ b/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php @@ -0,0 +1,181 @@ +alert = m::mock(AlertsMessageBag::class); + $this->settings = m::mock(Settings::class); + } + + /** + * Test that a missing user does not trigger this middleware. + */ + public function testRequestMissingUser() + { + $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test that the middleware is ignored on specific routes. + * + * @dataProvider ignoredRoutesDataProvider + */ + public function testRequestOnIgnoredRoute($route) + { + $this->request->shouldReceive('user')->withNoArgs()->once()->andReturn(true); + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn($route); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test disabled 2FA requirement. + */ + public function testTwoFactorRequirementDisabled() + { + $this->request->shouldReceive('user')->withNoArgs()->once()->andReturn(true); + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + + $this->settings->shouldReceive('get')->with('2fa', 0)->once()->andReturn(RequireTwoFactorAuthentication::LEVEL_NONE); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test 2FA required for admins as an administrative user who has 2FA disabled. + */ + public function testTwoFactorEnabledForAdminsAsAdminUserWith2FADisabled() + { + $user = factory(User::class)->make(['root_admin' => 1, 'use_totp' => 0]); + + $this->request->shouldReceive('user')->withNoArgs()->times(3)->andReturn($user); + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + + $this->settings->shouldReceive('get')->with('2fa', 0)->once()->andReturn(RequireTwoFactorAuthentication::LEVEL_ADMIN); + $this->alert->shouldReceive('danger')->with(trans('auth.2fa_must_be_enabled'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnSelf(); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertEquals(route('account.security'), $response->getTargetUrl()); + } + + /** + * Test 2FA required for admins as an administrative user who has 2FA enabled. + */ + public function testTwoFactorEnabledForAdminsAsAdminUserWith2FAEnabled() + { + $user = factory(User::class)->make(['root_admin' => 1, 'use_totp' => 1]); + + $this->request->shouldReceive('user')->withNoArgs()->times(3)->andReturn($user); + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + + $this->settings->shouldReceive('get')->with('2fa', 0)->once()->andReturn(RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test 2FA required for admins as an administrative user. + */ + public function testTwoFactorEnabledForAdminsAsNonAdmin() + { + $user = factory(User::class)->make(['root_admin' => 0]); + + $this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user); + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + + $this->settings->shouldReceive('get')->with('2fa', 0)->once()->andReturn(RequireTwoFactorAuthentication::LEVEL_ADMIN); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Test 2FA required for all users without 2FA enabled. + */ + public function testTwoFactorEnabledForAllUsersAsUserWith2FADisabled() + { + $user = factory(User::class)->make(['use_totp' => 0]); + + $this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user); + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + + $this->settings->shouldReceive('get')->with('2fa', 0)->once()->andReturn(RequireTwoFactorAuthentication::LEVEL_ALL); + $this->alert->shouldReceive('danger')->with(trans('auth.2fa_must_be_enabled'))->once()->andReturnSelf(); + $this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnSelf(); + + $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertEquals(route('account.security'), $response->getTargetUrl()); + } + + /** + * Test 2FA required for all users without 2FA enabled. + */ + public function testTwoFactorEnabledForAllUsersAsUserWith2FAEnabled() + { + $user = factory(User::class)->make(['use_totp' => 1]); + + $this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user); + $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route'); + + $this->settings->shouldReceive('get')->with('2fa', 0)->once()->andReturn(RequireTwoFactorAuthentication::LEVEL_ALL); + + $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); + } + + /** + * Routes that should be ignored. + * + * @return array + */ + public function ignoredRoutesDataProvider() + { + return [ + ['account.security'], + ['account.security.revoke'], + ['account.security.totp'], + ['account.security.totp.set'], + ['account.security.totp.disable'], + ['auth.totp'], + ['auth.logout'], + ]; + } + + /** + * Return an instance of the middleware using mocked dependencies. + * + * @return \Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication + */ + private function getMiddleware(): RequireTwoFactorAuthentication + { + return new RequireTwoFactorAuthentication($this->alert, $this->settings); + } +} From 3daade7fe524ff5fc673eb665b8a1b54a30d95da Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 3 Nov 2017 18:18:52 -0500 Subject: [PATCH 09/10] Fix tests --- tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php index ee56156d9..885108eca 100644 --- a/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php +++ b/tests/Unit/Http/Middleware/RedirectIfAuthenticatedTest.php @@ -30,7 +30,7 @@ class RedirectIfAuthenticatedTest extends MiddlewareTestCase public function testAuthenticatedUserIsRedirected() { $this->authManager->shouldReceive('guard')->with(null)->once()->andReturnSelf(); - $this->authManager->shouldReceive('check')->with(null)->once()->andReturn(true); + $this->authManager->shouldReceive('check')->withNoArgs()->once()->andReturn(true); $response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); $this->assertInstanceOf(RedirectResponse::class, $response); @@ -43,7 +43,7 @@ class RedirectIfAuthenticatedTest extends MiddlewareTestCase public function testNonAuthenticatedUserIsNotRedirected() { $this->authManager->shouldReceive('guard')->with(null)->once()->andReturnSelf(); - $this->authManager->shouldReceive('check')->with(null)->once()->andReturn(false); + $this->authManager->shouldReceive('check')->withNoArgs()->once()->andReturn(false); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } From 0b08c016680238864c5c43ba3705a31b9bb738c3 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 3 Nov 2017 20:40:51 -0500 Subject: [PATCH 10/10] Add beta warnings --- resources/themes/pterodactyl/base/api/index.blade.php | 5 +++++ resources/themes/pterodactyl/layouts/master.blade.php | 3 +++ .../themes/pterodactyl/partials/_internal/beta.blade.php | 9 +++++++++ 3 files changed, 17 insertions(+) create mode 100644 resources/themes/pterodactyl/partials/_internal/beta.blade.php diff --git a/resources/themes/pterodactyl/base/api/index.blade.php b/resources/themes/pterodactyl/base/api/index.blade.php index d06b590b4..a07657c96 100644 --- a/resources/themes/pterodactyl/base/api/index.blade.php +++ b/resources/themes/pterodactyl/base/api/index.blade.php @@ -20,7 +20,12 @@ @section('content')
    +
    + API functionality is disabled in this beta release. +
    +
    +

    @lang('base.api.index.list')

    diff --git a/resources/themes/pterodactyl/layouts/master.blade.php b/resources/themes/pterodactyl/layouts/master.blade.php index 62bb8c90d..ae1b1dc57 100644 --- a/resources/themes/pterodactyl/layouts/master.blade.php +++ b/resources/themes/pterodactyl/layouts/master.blade.php @@ -205,6 +205,9 @@
    +
    + @include('partials/_internal/beta') +
    @yield('content-header')
    diff --git a/resources/themes/pterodactyl/partials/_internal/beta.blade.php b/resources/themes/pterodactyl/partials/_internal/beta.blade.php new file mode 100644 index 000000000..d3ef3c46f --- /dev/null +++ b/resources/themes/pterodactyl/partials/_internal/beta.blade.php @@ -0,0 +1,9 @@ +@section('beta-notice') +
    +
    +
    + You are running a beta version of Pterodactyl Panel. Not all features are complete and bugs should be expected. Please report any bugs on Discord or via our Github issue tracker. +
    +
    +
    +@show