Merge pull request #716 from Pterodactyl/feature/code-cleanup

Cleanup code, add more unit tests, prepare for beta
This commit is contained in:
Dane Everitt 2017-11-03 21:42:03 -04:00 committed by GitHub
commit ea71deb8a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 2354 additions and 1135 deletions

View file

@ -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.

View file

@ -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.

View file

@ -1,35 +1,28 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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.

View file

@ -1,15 +1,9 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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'),
]]);

View file

@ -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));
}
}

View file

@ -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, '/') . '/',
]);

View file

@ -1,21 +1,15 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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);
}
}
}

View file

@ -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;

View file

@ -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);

View file

@ -1,24 +1,21 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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,28 +153,25 @@ 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
* @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): 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', []));
$this->alert->success(trans('server.users.user_assigned'))->flash();
return redirect()->route('server.subusers.view', [
'uuid' => $uuid,
'uuid' => $server->uuid,
'id' => $subuser->id,
]);
}
@ -200,20 +179,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);
}

View file

@ -1,166 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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);
}
}
}

View file

@ -1,17 +1,12 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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);

View file

@ -2,12 +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
{
@ -17,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,
];
/**
@ -35,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',
],
];
@ -61,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,
'server' => \Pterodactyl\Http\Middleware\ServerAuthenticate::class,
'subuser.auth' => \Pterodactyl\Http\Middleware\SubuserAccessAuthenticate::class,
'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class,
'auth' => Authenticate::class,
'auth.basic' => AuthenticateWithBasicAuth::class,
'guest' => RedirectIfAuthenticated::class,
'server' => AccessingValidServer::class,
'subuser.auth' => AuthenticateAsSubuser::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)
//

View file

@ -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);

View file

@ -3,41 +3,27 @@
namespace Pterodactyl\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request;
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, Closure $next)
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');
}
}

View file

@ -32,15 +32,19 @@ use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
class DaemonAuthenticate
{
/**
* @var array
*/
protected $except = ['daemon.configuration'];
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
*/
protected $repository;
private $repository;
/**
* Daemon routes that this middleware should be skipped on.
*
* @var array
*/
protected $except = [
'daemon.configuration',
];
/**
* DaemonAuthenticate constructor.
@ -63,6 +67,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)) {

View file

@ -10,35 +10,34 @@
namespace Pterodactyl\Http\Middleware;
use Closure;
use Pterodactyl\Models\Node;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
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,20 +47,18 @@ 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 AccessDeniedHttpException;
}
$node = Node::where('daemonSecret', $request->header('X-Access-Node'))->first();
if (! $node) {
return abort(401);
}
$node = $this->repository->findWhere(['daemonSecret' => $request->header('X-Access-Node')]);
$request->attributes->set('node', $node);
return $next($request);
}

View file

@ -11,6 +11,5 @@ class EncryptCookies extends BaseEncrypter
*
* @var array
*/
protected $except = [
];
protected $except = [];
}

View file

@ -9,14 +9,35 @@
namespace Pterodactyl\Http\Middleware;
use Auth;
use Closure;
use Session;
use Settings;
use Illuminate\Support\Facades\App;
use Illuminate\Http\Request;
use Illuminate\Foundation\Application;
use Illuminate\Contracts\Config\Repository;
class LanguageMiddleware
{
/**
* @var \Illuminate\Foundation\Application
*/
private $app;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
private $config;
/**
* LanguageMiddleware constructor.
*
* @param \Illuminate\Foundation\Application $app
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(Application $app, Repository $config)
{
$this->app = $app;
$this->config = $config;
}
/**
* Handle an incoming request.
*
@ -24,17 +45,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');
$this->app->setLocale($this->config->get('app.locale', 'en'));
return $next($request);
}

View file

@ -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\Auth\AuthManager
*/
private $authManager;
/**
* RedirectIfAuthenticated constructor.
*
* @param \Illuminate\Auth\AuthManager $authManager
*/
public function __construct(AuthManager $authManager)
{
$this->authManager = $authManager;
}
/**
* Handle an incoming request.
*
@ -15,10 +31,10 @@ 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()) {
return redirect(route('index'));
if ($this->authManager->guard($guard)->check()) {
return redirect()->route('index');
}
return $next($request);

View file

@ -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,29 +71,25 @@ 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()) {
return $next($request);
}
// Skip the 2FA pages
if (in_array($request->route()->getName(), $this->ignoreRoutes)) {
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);
@ -93,8 +97,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);
}
}

View file

@ -1,11 +1,4 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Middleware;
@ -13,36 +6,30 @@ 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;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class ServerAuthenticate
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;
/**
* ServerAuthenticate constructor.
* AccessingValidServer constructor.
*
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
@ -63,7 +50,7 @@ class ServerAuthenticate
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
* @return \Illuminate\Http\Response|mixed
*
* @throws \Illuminate\Auth\AuthenticationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
@ -72,10 +59,6 @@ class ServerAuthenticate
*/
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);
@ -96,9 +79,11 @@ class ServerAuthenticate
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);

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -1,51 +1,35 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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 Illuminate\Http\Request;
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 +39,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)
public function handle(Request $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);

View file

@ -1,42 +1,36 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* 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 Illuminate\Http\Request;
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;
}
/**
@ -50,12 +44,13 @@ 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 = $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 (is_null($subuser) || $subuser->server_id !== $server->id) {
throw new NotFoundHttpException;
}
@ -65,6 +60,8 @@ class SubuserBelongsToServer
}
}
$request->attributes->set('subuser', $subuser);
return $next($request);
}
}

View file

@ -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');
}
}

View file

@ -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.
*

View file

@ -0,0 +1,29 @@
<?php
namespace Pterodactyl\Http\Requests\Server;
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
abstract class ServerFormRequest extends FrontendUserFormRequest
{
/**
* Return the user permission to validate this request aganist.
*
* @return string
*/
abstract protected function permission(): string;
/**
* Determine if a user has permission to access this resource.
*
* @return bool
*/
public function authorize()
{
if (! parent::authorize()) {
return false;
}
return $this->user()->can($this->permission(), $this->attributes->get('server'));
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Pterodactyl\Http\Requests\Server\Subuser;
use Pterodactyl\Http\Requests\Server\ServerFormRequest;
class SubuserStoreFormRequest extends ServerFormRequest
{
/**
* Return the user permission to validate this request aganist.
*
* @return string
*/
protected function permission(): string
{
return 'create-subuser';
}
/**
* The rules to validate this request submission aganist.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|email',
'permissions' => 'present|array',
];
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Pterodactyl\Http\Requests\Server\Subuser;
use Pterodactyl\Http\Requests\Server\ServerFormRequest;
class SubuserUpdateFormRequest extends ServerFormRequest
{
/**
* Return the user permission to validate this request aganist.
*
* @return string
*/
protected function permission(): string
{
return 'edit-subuser';
}
/**
* The rules to validate this request submission aganist.
*
* @return array
*/
public function rules()
{
return [
'permissions' => 'present|array',
];
}
}

View file

@ -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;
}
}

View file

@ -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.
*

View file

@ -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;
}
/**

View file

@ -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;
}
/**

View file

@ -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.
@ -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);

View file

@ -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();

View file

@ -66,7 +66,7 @@ return [
|
*/
'locale' => 'en',
'locale' => env('APP_LOCALE', 'en'),
/*
|--------------------------------------------------------------------------

View file

@ -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,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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);
});

View file

@ -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'),
}),

View file

@ -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.',
];

View file

@ -20,7 +20,12 @@
@section('content')
<div class="row">
<div class="col-xs-12">
<div class="alert alert-danger">
API functionality is disabled in this beta release.
</div>
<div class="box">
<div class="overlay"></div>
<div class="box-header">
<h3 class="box-title">@lang('base.api.index.list')</h3>
<div class="box-tools">

View file

@ -205,6 +205,9 @@
</section>
</aside>
<div class="content-wrapper">
<section class="content-header">
@include('partials/_internal/beta')
</section>
<section class="content-header">
@yield('content-header')
</section>

View file

@ -0,0 +1,9 @@
@section('beta-notice')
<div class="row">
<div class="col-xs-12">
<div class="alert alert-warning no-margin-bottom">
<i class="fa fa-warning"></i> 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.
</div>
</div>
</div>
@show

View file

@ -57,14 +57,14 @@
<td class="middle hidden-xs">{{ $subuser->user->created_at }}</td>
@can('view-subuser', $server)
<td class="text-center middle">
<a href="{{ route('server.subusers.view', ['server' => $server->uuidShort, 'id' => $subuser->id]) }}">
<a href="{{ route('server.subusers.view', ['server' => $server->uuidShort, 'subuser' => $subuser->hashid]) }}">
<button class="btn btn-xs btn-primary">@lang('server.users.configure')</button>
</a>
</td>
@endcan
@can('delete-subuser', $server)
<td class="text-center middle">
<a href="#/delete/{{ $subuser->id }}" data-action="delete" data-id="{{ $subuser->id }}">
<a href="#/delete/{{ $subuser->hashid }}" data-action="delete" data-id="{{ $subuser->hashid }}">
<button class="btn btn-xs btn-danger">@lang('strings.revoke')</button>
</a>
</td>
@ -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'),

View file

@ -21,7 +21,7 @@
@section('content')
@can('edit-subuser', $server)
<form action="{{ route('server.subusers.view', [ 'uuid' => $server->uuidShort, 'id' => $subuser->id ]) }}" method="POST">
<form action="{{ route('server.subusers.view', [ 'uuid' => $server->uuidShort, 'subuser' => $subuser->hashid ]) }}" method="POST">
@endcan
<div class="row">
<div class="col-sm-12">

View file

@ -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');
});
});

View file

@ -0,0 +1,39 @@
<?php
namespace Tests\Assertions;
use PHPUnit\Framework\Assert;
trait MiddlewareAttributeAssertionsTrait
{
/**
* Assert a request has an attribute assigned to it.
*
* @param string $attribute
*/
public function assertRequestHasAttribute(string $attribute)
{
Assert::assertTrue($this->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));
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Tests\Traits\Http;
use Closure;
use Illuminate\Http\Request;
use BadFunctionCallException;
trait MocksMiddlewareClosure
{
/**
* Provide a closure to be used when validating that the response from the middleware
* is the same request object we passed into it.
*/
protected function getClosureAssertions(): Closure
{
if (is_null($this->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);
};
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace Tests\Traits\Http;
use Mockery as m;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use InvalidArgumentException;
use Symfony\Component\HttpFoundation\ParameterBag;
trait RequestMockHelpers
{
/**
* @var string
*/
private $requestMockClass = Request::class;
/**
* @var \Illuminate\Http\Request|\Mockery\Mock
*/
protected $request;
/**
* Set the class to mock for requests.
*
* @param string $class
*/
public function setRequestMockClass(string $class)
{
$this->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;
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace Tests\Unit\Http\Controllers;
use Mockery as m;
use Tests\TestCase;
use Tests\Traits\Http\RequestMockHelpers;
use Tests\Assertions\ControllerAssertionsTrait;
abstract class ControllerTestCase extends TestCase
{
use ControllerAssertionsTrait, RequestMockHelpers;
/**
* @var \Pterodactyl\Http\Controllers\Controller|\Mockery\Mock
*/
private $controller;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->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();
}
}

View file

@ -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]);
}
}

View file

@ -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]);
}
}

View file

@ -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]);
}
}

View file

@ -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]);
}
}

View file

@ -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,
]);
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Tests\Unit\Http\Middleware;
use Pterodactyl\Models\User;
use Pterodactyl\Http\Middleware\AdminAuthenticate;
use Symfony\Component\HttpKernel\Exception\HttpException;
class AdminAuthenticateTest extends MiddlewareTestCase
{
/**
* Test that an admin is authenticated.
*/
public function testAdminsAreAuthenticated()
{
$user = factory(User::class)->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();
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Tests\Unit\Http\Middleware;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Http\Middleware\Authenticate;
class AuthenticateTest extends MiddlewareTestCase
{
/**
* Test that a logged in user validates correctly.
*/
public function testLoggedInUser()
{
$this->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();
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Tests\Unit\Http\Middleware\Daemon;
use Mockery as m;
use Pterodactyl\Models\Node;
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 MiddlewareTestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->repository = m::mock(NodeRepositoryInterface::class);
}
/**
* 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->assertRequestHasAttribute('node');
$this->assertRequestAttributeEquals($model, '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);
}
}

View file

@ -0,0 +1,77 @@
<?php
namespace Tests\Unit\Http\Middleware;
use Mockery as m;
use Pterodactyl\Models\Node;
use Pterodactyl\Http\Middleware\DaemonAuthenticate;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
class DaemonAuthenticateTest extends MiddlewareTestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->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);
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Tests\Unit\Http\Middleware;
use Mockery as m;
use Illuminate\Foundation\Application;
use Illuminate\Contracts\Config\Repository;
use Pterodactyl\Http\Middleware\LanguageMiddleware;
class LanguageMiddlewareTest extends MiddlewareTestCase
{
/**
* @var \Illuminate\Foundation\Application|\Mockery\Mock
*/
private $appMock;
/**
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
*/
private $config;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->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);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Tests\Unit\Http\Middleware;
use Tests\TestCase;
use Tests\Traits\Http\RequestMockHelpers;
use Tests\Traits\Http\MocksMiddlewareClosure;
use Tests\Assertions\MiddlewareAttributeAssertionsTrait;
abstract class MiddlewareTestCase extends TestCase
{
use MiddlewareAttributeAssertionsTrait, MocksMiddlewareClosure, RequestMockHelpers;
/**
* Setup tests with a mocked request object and normal attributes.
*/
public function setUp()
{
parent::setUp();
$this->buildRequestMock();
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Tests\Unit\Http\Middleware;
use Mockery as m;
use Illuminate\Auth\AuthManager;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Http\Middleware\RedirectIfAuthenticated;
class RedirectIfAuthenticatedTest extends MiddlewareTestCase
{
/**
* @var \Illuminate\Auth\AuthManager|\Mockery\Mock
*/
private $authManager;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->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')->withNoArgs()->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')->withNoArgs()->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);
}
}

View file

@ -0,0 +1,181 @@
<?php
namespace Tests\Unit\Http\Middleware;
use Mockery as m;
use Pterodactyl\Models\User;
use Krucas\Settings\Settings;
use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase
{
/**
* @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock
*/
private $alert;
/**
* @var \Krucas\Settings\Settings|\Mockery\Mock
*/
private $settings;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->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);
}
}

View file

@ -0,0 +1,162 @@
<?php
namespace Tests\Unit\Http\Middleware\Server;
use Mockery as m;
use Illuminate\Http\Response;
use Pterodactyl\Models\Server;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\Config\Repository;
use Tests\Unit\Http\Middleware\MiddlewareTestCase;
use Pterodactyl\Http\Middleware\AccessingValidServer;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class AccessingValidServerTest extends MiddlewareTestCase
{
/**
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
*/
private $config;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* @var \Illuminate\Contracts\Session\Session|\Mockery\Mock
*/
private $session;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->config = m::mock(Repository::class);
$this->repository = m::mock(ServerRepositoryInterface::class);
$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->assertRequestHasAttribute('server');
$this->assertRequestAttributeEquals($model, '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);
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Tests\Unit\Http\Middleware\Server;
use Mockery as m;
use Pterodactyl\Models\Server;
use Illuminate\Contracts\Session\Session;
use Tests\Unit\Http\Middleware\MiddlewareTestCase;
use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
class AuthenticateAsSubuserTest extends MiddlewareTestCase
{
/**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService|\Mockery\Mock
*/
private $keyProviderService;
/**
* @var \Illuminate\Contracts\Session\Session|\Mockery\Mock
*/
private $session;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->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);
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Tests\Unit\Http\Middleware\Server;
use Mockery as m;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Database;
use Tests\Unit\Http\Middleware\MiddlewareTestCase;
use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
class DatabaseBelongsToServerTest extends MiddlewareTestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->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);
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Tests\Unit\Http\Middleware\Server;
use Mockery as m;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Schedule;
use Tests\Unit\Http\Middleware\MiddlewareTestCase;
use Pterodactyl\Contracts\Extensions\HashidsInterface;
use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer;
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
class ScheduleBelongsToServerTest extends MiddlewareTestCase
{
/**
* @var \Pterodactyl\Contracts\Extensions\HashidsInterface|\Mockery\Mock
*/
private $hashids;
/**
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface|\Mockery\Mock
*/
private $repository;
public function setUp()
{
parent::setUp();
$this->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);
}
}

View file

@ -0,0 +1,156 @@
<?php
namespace Tests\Unit\Http\Middleware\Server;
use Mockery as m;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Subuser;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\PterodactylException;
use Tests\Unit\Http\Middleware\MiddlewareTestCase;
use Pterodactyl\Contracts\Extensions\HashidsInterface;
use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer;
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
class SubuserBelongsToServerTest extends MiddlewareTestCase
{
/**
* @var \Pterodactyl\Contracts\Extensions\HashidsInterface|\Mockery\Mock
*/
private $hashids;
/**
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp()
{
parent::setUp();
$this->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);
}
}

View file

@ -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);
}
}

View file

@ -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
);
}
}