diff --git a/CHANGELOG.md b/CHANGELOG.md index 990cc2937..e44b65b9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,15 +12,22 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. * New CLI command to disabled 2-Factor Authentication on an account if necessary. * Ability to delete users and locations via the CLI. * You can now require 2FA for all users, admins only, or at will using a simple configuration in the Admin CP. -* Added ability to export and import service options and their associated settings and environment variables via the Admin CP. +* **Added ability to export and import service options and their associated settings and environment variables via the Admin CP.** +* Default allocation for a server can be changed on the front-end by users. This includes two new subuser permissions as well. +* Significant improvements to environment variable control for servers. Now ships with built-in abilities to define extra variables in the Panel's configuration file, or in-code for those heavily modifying the Panel. +* Quick link to server edit view in ACP on frontend when viewing servers. +* Databases created in the Panel now include `EXECUTE` privilege. ### Changed +* **Services renamed to Nests. Service Options renamed to Eggs.** 🥚 * Theme colors and login pages updated to give a more unique feel to the project. * Massive overhaul to the backend code that allows for much easier updating of core functionality as well as support for better testing. This overhaul also reduces complex code logic, and allows for faster response times in the application. * CLI commands updated to be easier to type, now stored in the `p:` namespace. * Logout icon is now more universal and not just a power icon. * Administrative logout notice now uses SWAL rather than a generic javascript popup. * 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. ### Fixed * Unable to change the daemon secret for a server via the Admin CP. @@ -28,6 +35,11 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. * Fixes a design-flaw in the allocation management part of nodes that would run a MySQL query for each port being allocated. This behavior is now changed to only execute one query to add multiple ports at once. * Attempting to create a server when no nodes are configured now redirects to the node creation page. * Fixes missing library issue for teamspeak when used with mariadb. +* Fixes inability to change the default port on front-end when viewing a server. +* Fixes bug preventing deletion of nests that have other nests referencing them as children. + +### Removed +* SFTP settings page now only displays connection address and username. Password setting was removed as it is no longer necessary with Daemon changes. ## v0.6.4 (Courageous Carniadactylus) ### Fixed diff --git a/app/Contracts/Repository/DatabaseRepositoryInterface.php b/app/Contracts/Repository/DatabaseRepositoryInterface.php index ca5379df1..1e90d0e04 100644 --- a/app/Contracts/Repository/DatabaseRepositoryInterface.php +++ b/app/Contracts/Repository/DatabaseRepositoryInterface.php @@ -9,8 +9,35 @@ namespace Pterodactyl\Contracts\Repository; +use Illuminate\Support\Collection; + interface DatabaseRepositoryInterface extends RepositoryInterface { + const DEFAULT_CONNECTION_NAME = 'dynamic'; + + /** + * Set the connection name to execute statements against. + * + * @param string $connection + * @return $this + */ + public function setConnection(string $connection); + + /** + * Return the connection to execute statements aganist. + * + * @return string + */ + public function getConnection(): string; + + /** + * Return all of the databases belonging to a server. + * + * @param int $server + * @return \Illuminate\Support\Collection + */ + public function getDatabasesForServer(int $server): Collection; + /** * Create a new database if it does not already exist on the host with * the provided details. @@ -26,58 +53,52 @@ interface DatabaseRepositoryInterface extends RepositoryInterface /** * Create a new database on a given connection. * - * @param string $database - * @param null|string $connection + * @param string $database * @return bool */ - public function createDatabase($database, $connection = null); + public function createDatabase($database); /** * Create a new database user on a given connection. * - * @param string $username - * @param string $remote - * @param string $password - * @param null|string $connection + * @param string $username + * @param string $remote + * @param string $password * @return bool */ - public function createUser($username, $remote, $password, $connection = null); + public function createUser($username, $remote, $password); /** * Give a specific user access to a given database. * - * @param string $database - * @param string $username - * @param string $remote - * @param null|string $connection + * @param string $database + * @param string $username + * @param string $remote * @return bool */ - public function assignUserToDatabase($database, $username, $remote, $connection = null); + public function assignUserToDatabase($database, $username, $remote); /** * Flush the privileges for a given connection. * - * @param null|string $connection * @return mixed */ - public function flush($connection = null); + public function flush(); /** * Drop a given database on a specific connection. * - * @param string $database - * @param null|string $connection + * @param string $database * @return bool */ - public function dropDatabase($database, $connection = null); + public function dropDatabase($database); /** * Drop a given user on a specific connection. * - * @param string $username - * @param string $remote - * @param null|string $connection + * @param string $username + * @param string $remote * @return mixed */ - public function dropUser($username, $remote, $connection = null); + public function dropUser($username, $remote); } diff --git a/app/Contracts/Repository/EggVariableRepositoryInterface.php b/app/Contracts/Repository/EggVariableRepositoryInterface.php index afaf7463b..77b46f96d 100644 --- a/app/Contracts/Repository/EggVariableRepositoryInterface.php +++ b/app/Contracts/Repository/EggVariableRepositoryInterface.php @@ -9,6 +9,16 @@ namespace Pterodactyl\Contracts\Repository; +use Illuminate\Support\Collection; + interface EggVariableRepositoryInterface extends RepositoryInterface { + /** + * Return editable variables for a given egg. Editable variables must be set to + * user viewable in order to be picked up by this function. + * + * @param int $egg + * @return \Illuminate\Support\Collection + */ + public function getEditableVariables(int $egg): Collection; } diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php index 73cb5c71e..9e597bb73 100644 --- a/app/Contracts/Repository/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/ServerRepositoryInterface.php @@ -9,6 +9,7 @@ namespace Pterodactyl\Contracts\Repository; +use Pterodactyl\Models\Server; use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; interface ServerRepositoryInterface extends RepositoryInterface, SearchableInterface @@ -53,14 +54,26 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter public function getVariablesWithValues($id, $returnAsObject = false); /** - * Return enough data to be used for the creation of a server via the daemon. + * Get the primary allocation for a given server. If a model is passed into + * the function, load the allocation relationship onto it. Otherwise, find and + * return the server from the database. * - * @param int $id - * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model + * @param int|\Pterodactyl\Models\Server $server + * @param bool $refresh + * @return \Pterodactyl\Models\Server * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getDataForCreation($id); + public function getPrimaryAllocation($server, bool $refresh = false): Server; + + /** + * Return enough data to be used for the creation of a server via the daemon. + * + * @param \Pterodactyl\Models\Server $server + * @param bool $refresh + * @return \Pterodactyl\Models\Server + */ + public function getDataForCreation(Server $server, bool $refresh = false): Server; /** * Return a server as well as associated databases and their hosts. diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index 80c5771a5..aa18a1c1b 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -28,9 +28,9 @@ class DisplayException extends PterodactylException * @param string $message * @param Throwable|null $previous * @param string $level - * @internal param mixed $log + * @param int $code */ - public function __construct($message, Throwable $previous = null, $level = self::LEVEL_ERROR) + public function __construct($message, Throwable $previous = null, $level = self::LEVEL_ERROR, $code = 0) { $this->level = $level; @@ -38,7 +38,7 @@ class DisplayException extends PterodactylException Log::{$level}($previous); } - parent::__construct($message); + parent::__construct($message, $code, $previous); } /** diff --git a/app/Exceptions/Service/Allocation/AllocationDoesNotBelongToServerException.php b/app/Exceptions/Service/Allocation/AllocationDoesNotBelongToServerException.php new file mode 100644 index 000000000..81f056b56 --- /dev/null +++ b/app/Exceptions/Service/Allocation/AllocationDoesNotBelongToServerException.php @@ -0,0 +1,9 @@ +authenticationService = $authenticationService; + } + + /** + * Authenticate a set of credentials and return the associated server details + * for a SFTP connection on the daemon. + * + * @param \Pterodactyl\Http\Requests\API\Remote\SftpAuthenticationFormRequest $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function index(SftpAuthenticationFormRequest $request): JsonResponse + { + $connection = explode('.', $request->input('username')); + $this->incrementLoginAttempts($request); + + if ($this->hasTooManyLoginAttempts($request)) { + return response()->json([ + 'error' => 'Logins throttled.', + ], 429); + } + + try { + $data = $this->authenticationService->handle( + array_get($connection, 0), + $request->input('password'), + object_get($request->attributes->get('node'), 'id', 0), + array_get($connection, 1) + ); + + $this->clearLoginAttempts($request); + } catch (AuthenticationException $exception) { + return response()->json([ + 'error' => 'Invalid credentials.', + ], 403); + } catch (RecordNotFoundException $exception) { + return response()->json([ + 'error' => 'Invalid server.', + ], 404); + } + + return response()->json($data); + } + + /** + * Get the throttle key for the given request. + * + * @param \Illuminate\Http\Request $request + * @return string + */ + protected function throttleKey(Request $request) + { + return strtolower(array_get(explode('.', $request->input('username')), 0) . '|' . $request->ip()); + } +} diff --git a/app/Http/Controllers/Admin/DatabaseController.php b/app/Http/Controllers/Admin/DatabaseController.php index 9eac33f69..02271d699 100644 --- a/app/Http/Controllers/Admin/DatabaseController.php +++ b/app/Http/Controllers/Admin/DatabaseController.php @@ -9,11 +9,15 @@ namespace Pterodactyl\Http\Controllers\Admin; -use Pterodactyl\Models\DatabaseHost; +use PDOException; +use Illuminate\View\View; +use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Database\DatabaseHostService; +use Pterodactyl\Services\Databases\Hosts\HostUpdateService; use Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest; +use Pterodactyl\Services\Databases\Hosts\HostCreationService; +use Pterodactyl\Services\Databases\Hosts\HostDeletionService; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; @@ -22,41 +26,57 @@ class DatabaseController extends Controller /** * @var \Prologue\Alerts\AlertsMessageBag */ - protected $alert; + private $alert; + + /** + * @var \Pterodactyl\Services\Databases\Hosts\HostCreationService + */ + private $creationService; + + /** + * @var \Pterodactyl\Services\Databases\Hosts\HostDeletionService + */ + private $deletionService; /** * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface */ - protected $locationRepository; + private $locationRepository; /** * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface */ - protected $repository; + private $repository; /** - * @var \Pterodactyl\Services\Database\DatabaseHostService + * @var \Pterodactyl\Services\Databases\Hosts\HostUpdateService */ - protected $service; + private $updateService; /** * DatabaseController constructor. * * @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository - * @param \Pterodactyl\Services\Database\DatabaseHostService $service + * @param \Pterodactyl\Services\Databases\Hosts\HostCreationService $creationService + * @param \Pterodactyl\Services\Databases\Hosts\HostDeletionService $deletionService + * @param \Pterodactyl\Services\Databases\Hosts\HostUpdateService $updateService * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository */ public function __construct( AlertsMessageBag $alert, DatabaseHostRepositoryInterface $repository, - DatabaseHostService $service, + HostCreationService $creationService, + HostDeletionService $deletionService, + HostUpdateService $updateService, LocationRepositoryInterface $locationRepository ) { $this->alert = $alert; + $this->creationService = $creationService; + $this->deletionService = $deletionService; $this->repository = $repository; - $this->service = $service; $this->locationRepository = $locationRepository; + $this->updateService = $updateService; } /** @@ -64,7 +84,7 @@ class DatabaseController extends Controller * * @return \Illuminate\View\View */ - public function index() + public function index(): View { return view('admin.databases.index', [ 'locations' => $this->locationRepository->getAllWithNodes(), @@ -80,7 +100,7 @@ class DatabaseController extends Controller * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function view($host) + public function view($host): View { return view('admin.databases.view', [ 'locations' => $this->locationRepository->getAllWithNodes(), @@ -94,42 +114,41 @@ class DatabaseController extends Controller * @param \Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest $request * @return \Illuminate\Http\RedirectResponse * - * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function create(DatabaseHostFormRequest $request) + public function create(DatabaseHostFormRequest $request): RedirectResponse { try { - $host = $this->service->create($request->normalize()); - $this->alert->success('Successfully created a new database host on the system.')->flash(); - - return redirect()->route('admin.databases.view', $host->id); - } catch (\PDOException $ex) { + $host = $this->creationService->handle($request->normalize()); + } catch (PDOException $ex) { $this->alert->danger($ex->getMessage())->flash(); + + return redirect()->route('admin.databases'); } - return redirect()->route('admin.databases'); + $this->alert->success('Successfully created a new database host on the system.')->flash(); + + return redirect()->route('admin.databases.view', $host->id); } /** * Handle updating database host. * * @param \Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest $request - * @param \Pterodactyl\Models\DatabaseHost $host + * @param int $host * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(DatabaseHostFormRequest $request, DatabaseHost $host) + public function update(DatabaseHostFormRequest $request, int $host): RedirectResponse { - if ($request->input('action') === 'delete') { - return $this->delete($host); - } - try { - $host = $this->service->update($host->id, $request->normalize()); + $host = $this->updateService->handle($host, $request->normalize()); $this->alert->success('Database host was updated successfully.')->flash(); - } catch (\PDOException $ex) { + } catch (PDOException $ex) { $this->alert->danger($ex->getMessage())->flash(); } @@ -139,14 +158,14 @@ class DatabaseController extends Controller /** * Handle request to delete a database host. * - * @param \Pterodactyl\Models\DatabaseHost $host + * @param int $host * @return \Illuminate\Http\RedirectResponse * - * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function delete(DatabaseHost $host) + public function delete(int $host): RedirectResponse { - $this->service->delete($host->id); + $this->deletionService->handle($host); $this->alert->success('The requested database host has been deleted from the system.')->flash(); return redirect()->route('admin.databases'); diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 5867d4788..f79d257a4 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -11,6 +11,7 @@ namespace Pterodactyl\Http\Controllers\Admin; use Javascript; use Illuminate\Http\Request; +use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\DisplayException; @@ -22,12 +23,13 @@ use Pterodactyl\Services\Servers\ServerDeletionService; use Pterodactyl\Services\Servers\ReinstallServerService; use Pterodactyl\Services\Servers\ContainerRebuildService; use Pterodactyl\Services\Servers\BuildModificationService; -use Pterodactyl\Services\Database\DatabaseManagementService; +use Pterodactyl\Services\Databases\DatabasePasswordService; use Pterodactyl\Services\Servers\DetailsModificationService; use Pterodactyl\Services\Servers\StartupModificationService; use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; +use Pterodactyl\Services\Databases\DatabaseManagementService; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; @@ -67,10 +69,15 @@ class ServersController extends Controller protected $databaseRepository; /** - * @var \Pterodactyl\Services\Database\DatabaseManagementService + * @var \Pterodactyl\Services\Databases\DatabaseManagementService */ protected $databaseManagementService; + /** + * @var \Pterodactyl\Services\Databases\DatabasePasswordService + */ + protected $databasePasswordService; + /** * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface */ @@ -135,7 +142,8 @@ class ServersController extends Controller * @param \Illuminate\Contracts\Config\Repository $config * @param \Pterodactyl\Services\Servers\ContainerRebuildService $containerRebuildService * @param \Pterodactyl\Services\Servers\ServerCreationService $service - * @param \Pterodactyl\Services\Database\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository * @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository * @param \Pterodactyl\Services\Servers\ServerDeletionService $deletionService @@ -156,6 +164,7 @@ class ServersController extends Controller ContainerRebuildService $containerRebuildService, ServerCreationService $service, DatabaseManagementService $databaseManagementService, + DatabasePasswordService $databasePasswordService, DatabaseRepositoryInterface $databaseRepository, DatabaseHostRepository $databaseHostRepository, ServerDeletionService $deletionService, @@ -173,9 +182,10 @@ class ServersController extends Controller $this->buildModificationService = $buildModificationService; $this->config = $config; $this->containerRebuildService = $containerRebuildService; - $this->databaseManagementService = $databaseManagementService; - $this->databaseRepository = $databaseRepository; $this->databaseHostRepository = $databaseHostRepository; + $this->databaseManagementService = $databaseManagementService; + $this->databasePasswordService = $databasePasswordService; + $this->databaseRepository = $databaseRepository; $this->detailsModificationService = $detailsModificationService; $this->deletionService = $deletionService; $this->locationRepository = $locationRepository; @@ -561,10 +571,8 @@ class ServersController extends Controller */ public function saveStartup(Request $request, Server $server) { - $this->startupModificationService->isAdmin()->handle( - $server, - $request->except('_token') - ); + $this->startupModificationService->setUserLevel(User::USER_LEVEL_ADMIN); + $this->startupModificationService->handle($server, $request->except('_token')); $this->alert->success(trans('admin/server.alerts.startup_changed'))->flash(); return redirect()->route('admin.servers.view.startup', $server->id); @@ -609,7 +617,7 @@ class ServersController extends Controller ['id', '=', $request->input('database')], ]); - $this->databaseManagementService->changePassword($database->id, str_random(20)); + $this->databasePasswordService->handle($database, str_random(20)); return response('', 204); } diff --git a/app/Http/Controllers/Server/DatabaseController.php b/app/Http/Controllers/Server/DatabaseController.php new file mode 100644 index 000000000..06636c4c0 --- /dev/null +++ b/app/Http/Controllers/Server/DatabaseController.php @@ -0,0 +1,77 @@ +passwordService = $passwordService; + $this->repository = $repository; + } + + /** + * Render the database listing for a server. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request): View + { + $server = $request->attributes->get('server'); + $this->authorize('view-databases', $server); + $this->setRequest($request)->injectJavascript(); + + return view('server.databases.index', [ + 'databases' => $this->repository->getDatabasesForServer($server->id), + ]); + } + + /** + * Handle a request to update the password for a specific database. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(Request $request): JsonResponse + { + $this->authorize('reset-db-password', $request->attributes->get('server')); + + $password = str_random(20); + $this->passwordService->handle($request->attributes->get('database'), $password); + + return response()->json(['password' => $password]); + } +} diff --git a/app/Http/Controllers/Server/ServerController.php b/app/Http/Controllers/Server/ServerController.php deleted file mode 100644 index 9b4208319..000000000 --- a/app/Http/Controllers/Server/ServerController.php +++ /dev/null @@ -1,185 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Server; - -use Log; -use Alert; -use Pterodactyl\Models; -use Illuminate\Http\Request; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Exceptions\DisplayValidationException; - -class ServerController extends Controller -{ - /** - * Returns the allocation overview for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getAllocation(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('view-allocation', $server); - $server->js(); - - return view('server.settings.allocation', [ - 'server' => $server->load(['allocations' => function ($query) { - $query->orderBy('ip', 'asc'); - $query->orderBy('port', 'asc'); - }]), - 'node' => $server->node, - ]); - } - - /** - * Returns the startup overview for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getStartup(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('view-startup', $server); - - $server->load(['node', 'allocation', 'variables']); - $variables = Models\EggVariable::where('option_id', $server->option_id)->get(); - - $replacements = [ - '{{SERVER_MEMORY}}' => $server->memory, - '{{SERVER_IP}}' => $server->allocation->ip, - '{{SERVER_PORT}}' => $server->allocation->port, - ]; - - $processed = str_replace(array_keys($replacements), array_values($replacements), $server->startup); - - foreach ($variables as $var) { - if ($var->user_viewable) { - $serverVar = $server->variables->where('variable_id', $var->id)->first(); - $var->server_set_value = $serverVar->variable_value ?? $var->default_value; - } else { - $var->server_set_value = '[hidden]'; - } - - $processed = str_replace('{{' . $var->env_variable . '}}', $var->server_set_value, $processed); - } - - $server->js(); - - return view('server.settings.startup', [ - 'server' => $server, - 'node' => $server->node, - 'variables' => $variables->where('user_viewable', 1), - 'service' => $server->service, - 'processedStartup' => $processed, - ]); - } - - /** - * Returns the database overview for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getDatabases(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('view-databases', $server); - - $server->load('node', 'databases.host'); - $server->js(); - - return view('server.settings.databases', [ - 'server' => $server, - 'node' => $server->node, - 'databases' => $server->databases, - ]); - } - - /** - * Returns the SFTP overview for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\View\View - */ - public function getSFTP(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('view-sftp', $server); - $server->js(); - - return view('server.settings.sftp', [ - 'server' => $server, - 'node' => $server->node, - ]); - } - - /** - * Handles changing the SFTP password for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\RedirectResponse - */ - public function postSettingsSFTP(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('reset-sftp', $server); - - try { - $repo = new ServerRepository; - $repo->updateSFTPPassword($server->id, $request->input('sftp_pass')); - Alert::success('Successfully updated this servers SFTP password.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.settings.sftp', $uuid)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unknown error occured while attempting to update this server\'s SFTP settings.')->flash(); - } - - return redirect()->route('server.settings.sftp', $uuid); - } - - /** - * Handles changing the startup settings for a server. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\RedirectResponse - */ - public function postSettingsStartup(Request $request, $uuid) - { - $server = Models\Server::byUuid($uuid); - $this->authorize('edit-startup', $server); - - try { - $repo = new ServerRepository; - $repo->updateStartup($server->id, $request->except('_token')); - Alert::success('Server startup variables were successfully updated.')->flash(); - } catch (DisplayValidationException $ex) { - return redirect()->route('server.settings.startup', $uuid)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. Please try again.')->flash(); - } - - return redirect()->route('server.settings.startup', $uuid); - } -} diff --git a/app/Http/Controllers/Server/Settings/AllocationController.php b/app/Http/Controllers/Server/Settings/AllocationController.php new file mode 100644 index 000000000..18a42f963 --- /dev/null +++ b/app/Http/Controllers/Server/Settings/AllocationController.php @@ -0,0 +1,97 @@ +defaultAllocationService = $defaultAllocationService; + $this->hashids = $hashids; + $this->repository = $repository; + } + + /** + * Render the allocation management overview page for a server. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function index(Request $request): View + { + $server = $request->attributes->get('server'); + $this->authorize('view-allocations', $server); + $this->setRequest($request)->injectJavascript(); + + return view('server.settings.allocation', [ + 'allocations' => $this->repository->findWhere([['server_id', '=', $server->id]]), + ]); + } + + /** + * Update the default allocation for a server. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(Request $request): JsonResponse + { + $server = $request->attributes->get('server'); + $this->authorize('edit-allocation', $server); + + $allocation = $this->hashids->decodeFirst($request->input('allocation'), 0); + + try { + $this->defaultAllocationService->handle($server->id, $allocation); + } catch (AllocationDoesNotBelongToServerException $exception) { + return response()->json(['error' => 'No matching allocation was located for this server.'], 404); + } + + return response()->json(); + } +} diff --git a/app/Http/Controllers/Server/Settings/SftpController.php b/app/Http/Controllers/Server/Settings/SftpController.php new file mode 100644 index 000000000..b128ba5c9 --- /dev/null +++ b/app/Http/Controllers/Server/Settings/SftpController.php @@ -0,0 +1,26 @@ +setRequest($request)->injectJavascript(); + + return view('server.settings.sftp'); + } +} diff --git a/app/Http/Controllers/Server/Settings/StartupController.php b/app/Http/Controllers/Server/Settings/StartupController.php new file mode 100644 index 000000000..5d299c42e --- /dev/null +++ b/app/Http/Controllers/Server/Settings/StartupController.php @@ -0,0 +1,94 @@ +alert = $alert; + $this->commandViewService = $commandViewService; + $this->modificationService = $modificationService; + } + + /** + * Render the server startup page. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\View + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index(Request $request) + { + $server = $request->attributes->get('server'); + $this->authorize('view-startup', $server); + $this->injectJavascript(); + + $data = $this->commandViewService->handle($server->id); + + return view('server.settings.startup', [ + 'variables' => $data->get('variables'), + 'server_values' => $data->get('server_values'), + 'startup' => $data->get('startup'), + ]); + } + + /** + * Handle request to update the startup variables for a server. Authorization + * is handled in the form request. + * + * @param \Pterodactyl\Http\Requests\Server\UpdateStartupParametersFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(UpdateStartupParametersFormRequest $request): RedirectResponse + { + $this->modificationService->setUserLevel(User::USER_LEVEL_USER); + $this->modificationService->handle($request->attributes->get('server'), $request->normalize()); + $this->alert->success(trans('server.config.startup.edited'))->flash(); + + return redirect()->route('server.settings.startup', ['server' => $request->attributes->get('server')->uuidShort]); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b1812a9d3..22e90a903 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -5,6 +5,9 @@ namespace Pterodactyl\Http; use Pterodactyl\Http\Middleware\DaemonAuthenticate; use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Routing\Middleware\SubstituteBindings; +use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer; +use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; +use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; class Kernel extends HttpKernel { @@ -63,7 +66,6 @@ class Kernel extends HttpKernel 'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class, 'server' => \Pterodactyl\Http\Middleware\ServerAuthenticate::class, 'subuser.auth' => \Pterodactyl\Http\Middleware\SubuserAccessAuthenticate::class, - 'subuser' => \Pterodactyl\Http\Middleware\Server\SubuserAccess::class, 'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class, 'daemon-old' => DaemonAuthenticate::class, 'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, @@ -71,6 +73,13 @@ class Kernel extends HttpKernel 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'recaptcha' => \Pterodactyl\Http\Middleware\VerifyReCaptcha::class, - 'schedule' => \Pterodactyl\Http\Middleware\Server\ScheduleAccess::class, + + // Server specific middleware (used for authenticating access to resources) + // + // These are only used for individual server authentication, and not gloabl + // actions from other resources. They are defined in the route files. + 'server..database' => DatabaseBelongsToServer::class, + 'server..subuser' => SubuserBelongsToServer::class, + 'server..schedule' => ScheduleBelongsToServer::class, ]; } diff --git a/app/Http/Middleware/Daemon/DaemonAuthenticate.php b/app/Http/Middleware/Daemon/DaemonAuthenticate.php index 2804fa923..2572ba854 100644 --- a/app/Http/Middleware/Daemon/DaemonAuthenticate.php +++ b/app/Http/Middleware/Daemon/DaemonAuthenticate.php @@ -75,7 +75,7 @@ class DaemonAuthenticate throw new HttpException(403); } - $request->attributes->set('node.model', $node); + $request->attributes->set('node', $node); return $next($request); } diff --git a/app/Http/Middleware/Server/DatabaseBelongsToServer.php b/app/Http/Middleware/Server/DatabaseBelongsToServer.php new file mode 100644 index 000000000..bc31c29c8 --- /dev/null +++ b/app/Http/Middleware/Server/DatabaseBelongsToServer.php @@ -0,0 +1,51 @@ +repository = $repository; + } + + /** + * Check if a database being requested belongs to the currently loaded server. + * If it does not, throw a 404 error, otherwise continue on with the request + * and set an attribute with the database. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(Request $request, Closure $next) + { + $server = $request->attributes->get('server'); + + $database = $this->repository->find($request->input('database')); + if ($database->server_id !== $server->id) { + throw new NotFoundHttpException; + } + + $request->attributes->set('database', $database); + + return $next($request); + } +} diff --git a/app/Http/Middleware/Server/ScheduleAccess.php b/app/Http/Middleware/Server/ScheduleBelongsToServer.php similarity index 98% rename from app/Http/Middleware/Server/ScheduleAccess.php rename to app/Http/Middleware/Server/ScheduleBelongsToServer.php index b54b07d47..145429f8c 100644 --- a/app/Http/Middleware/Server/ScheduleAccess.php +++ b/app/Http/Middleware/Server/ScheduleBelongsToServer.php @@ -14,7 +14,7 @@ use Illuminate\Contracts\Session\Session; use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; -class ScheduleAccess +class ScheduleBelongsToServer { /** * @var \Pterodactyl\Contracts\Extensions\HashidsInterface diff --git a/app/Http/Middleware/Server/SubuserAccess.php b/app/Http/Middleware/Server/SubuserBelongsToServer.php similarity index 98% rename from app/Http/Middleware/Server/SubuserAccess.php rename to app/Http/Middleware/Server/SubuserBelongsToServer.php index 85ebe2640..b18620f51 100644 --- a/app/Http/Middleware/Server/SubuserAccess.php +++ b/app/Http/Middleware/Server/SubuserBelongsToServer.php @@ -15,7 +15,7 @@ use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -class SubuserAccess +class SubuserBelongsToServer { /** * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface diff --git a/app/Http/Middleware/ServerAuthenticate.php b/app/Http/Middleware/ServerAuthenticate.php index 7df3d110a..538382f23 100644 --- a/app/Http/Middleware/ServerAuthenticate.php +++ b/app/Http/Middleware/ServerAuthenticate.php @@ -105,8 +105,13 @@ class ServerAuthenticate } // Store the server in the session. + // @todo remove from session. use request attributes. $this->session->now('server_data.model', $server); + // Add server to the request attributes. This will replace sessions + // as files are updated. + $request->attributes->set('server', $server); + return $next($request); } } diff --git a/app/Http/Middleware/SubuserAccessAuthenticate.php b/app/Http/Middleware/SubuserAccessAuthenticate.php index fd4b8cf4d..30a906884 100644 --- a/app/Http/Middleware/SubuserAccessAuthenticate.php +++ b/app/Http/Middleware/SubuserAccessAuthenticate.php @@ -60,6 +60,7 @@ class SubuserAccessAuthenticate 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.'); } diff --git a/app/Http/Requests/API/Remote/SftpAuthenticationFormRequest.php b/app/Http/Requests/API/Remote/SftpAuthenticationFormRequest.php new file mode 100644 index 000000000..5d82f55c7 --- /dev/null +++ b/app/Http/Requests/API/Remote/SftpAuthenticationFormRequest.php @@ -0,0 +1,44 @@ + 'required|string', + 'password' => 'required|string', + ]; + } + + /** + * Return only the fields that we are interested in from the request. + * This will include empty fields as a null value. + * + * @return array + */ + public function normalize() + { + return $this->only( + array_keys($this->rules()) + ); + } +} diff --git a/app/Http/Requests/Server/UpdateStartupParametersFormRequest.php b/app/Http/Requests/Server/UpdateStartupParametersFormRequest.php new file mode 100644 index 000000000..41c15103f --- /dev/null +++ b/app/Http/Requests/Server/UpdateStartupParametersFormRequest.php @@ -0,0 +1,61 @@ +user()->can('edit-startup', $this->attributes->get('server')); + } + + /** + * Validate that all of the required fields were passed and that the environment + * variable values meet the defined criteria for those fields. + * + * @return array + */ + public function rules() + { + $repository = $this->container->make(EggVariableRepositoryInterface::class); + + $variables = $repository->getEditableVariables($this->attributes->get('server')->egg_id); + $rules = $variables->mapWithKeys(function ($variable) { + $this->validationAttributes['environment.' . $variable->env_variable] = $variable->name; + + return ['environment.' . $variable->env_variable => $variable->rules]; + })->toArray(); + + return array_merge($rules, [ + 'environment' => 'required|array', + ]); + } + + /** + * Return attributes to provide better naming conventions for error messages. + * + * @return array + */ + public function attributes() + { + return $this->validationAttributes; + } +} diff --git a/app/Http/ViewComposers/Server/ServerDataComposer.php b/app/Http/ViewComposers/Server/ServerDataComposer.php index 7da647587..9e1858645 100644 --- a/app/Http/ViewComposers/Server/ServerDataComposer.php +++ b/app/Http/ViewComposers/Server/ServerDataComposer.php @@ -1,32 +1,25 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\ViewComposers\Server; use Illuminate\View\View; -use Illuminate\Contracts\Session\Session; +use Illuminate\Http\Request; class ServerDataComposer { /** - * @var \Illuminate\Contracts\Session\Session + * @var \Illuminate\Http\Request */ - protected $session; + protected $request; /** * ServerDataComposer constructor. * - * @param \Illuminate\Contracts\Session\Session $session + * @param \Illuminate\Http\Request $request */ - public function __construct(Session $session) + public function __construct(Request $request) { - $this->session = $session; + $this->request = $request; } /** @@ -36,10 +29,10 @@ class ServerDataComposer */ public function compose(View $view) { - $data = $this->session->get('server_data'); + $server = $this->request->get('server'); - $view->with('server', array_get($data, 'model')); - $view->with('node', object_get($data['model'], 'node')); - $view->with('daemon_token', array_get($data, 'token')); + $view->with('server', $server); + $view->with('node', object_get($server, 'node')); + $view->with('daemon_token', $this->request->get('server_token')); } } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 9593a7744..bb77647d9 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -64,6 +64,16 @@ class Allocation extends Model implements CleansAttributes, ValidableContract 'server_id' => 'nullable|exists:servers,id', ]; + /** + * Return a hashid encoded string to represent the ID of the allocation. + * + * @return string + */ + public function getHashidAttribute() + { + return app()->make('hashids')->encode($this->id); + } + /** * Accessor to automatically provide the IP alias if defined. * diff --git a/app/Models/Node.php b/app/Models/Node.php index 368c6f3d8..cc22a724e 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -20,6 +20,8 @@ class Node extends Model implements CleansAttributes, ValidableContract { use Eloquence, Notifiable, Validable; + const DAEMON_SECRET_LENGTH = 36; + /** * The table associated with the model. * @@ -144,13 +146,23 @@ class Node extends Model implements CleansAttributes, ValidableContract ], ], 'docker' => [ + 'container' => [ + 'user' => null, + ], + 'network' => [ + 'name' => 'pterodactyl_nw', + ], 'socket' => '/var/run/docker.sock', 'autoupdate_images' => true, ], 'sftp' => [ 'path' => $this->daemonBase, + 'ip' => '0.0.0.0', 'port' => $this->daemonSFTP, - 'container' => 'ptdl-sftp', + 'keypair' => [ + 'bits' => 2048, + 'e' => 65537, + ], ], 'logger' => [ 'path' => 'logs/', diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 1fc57cc57..61b67e487 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -86,7 +86,8 @@ class Permission extends Model implements CleansAttributes, ValidableContract 'delete-subuser' => null, ], 'server' => [ - 'set-connection' => null, + 'view-allocations' => null, + 'edit-allocation' => null, 'view-startup' => null, 'edit-startup' => null, ], diff --git a/app/Models/Server.php b/app/Models/Server.php index 09563baf1..04ac19e43 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -29,19 +29,12 @@ class Server extends Model implements CleansAttributes, ValidableContract */ protected $table = 'servers'; - /** - * The attributes excluded from the model's JSON form. - * - * @var array - */ - protected $hidden = ['sftp_password']; - /** * The attributes that should be mutated to dates. * * @var array */ - protected $dates = ['deleted_at']; + protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'deleted_at']; /** * Always eager load these relationships on the model. @@ -55,7 +48,7 @@ class Server extends Model implements CleansAttributes, ValidableContract * * @var array */ - protected $guarded = ['id', 'installed', 'created_at', 'updated_at', 'deleted_at']; + protected $guarded = ['id', 'installed', self::CREATED_AT, self::UPDATED_AT, 'deleted_at']; /** * @var array @@ -73,8 +66,6 @@ class Server extends Model implements CleansAttributes, ValidableContract 'node_id' => 'required', 'allocation_id' => 'required', 'pack_id' => 'sometimes', - 'auto_deploy' => 'sometimes', - 'custom_id' => 'sometimes', 'skip_scripts' => 'sometimes', ]; @@ -95,10 +86,7 @@ class Server extends Model implements CleansAttributes, ValidableContract 'nest_id' => 'exists:nests,id', 'egg_id' => 'exists:eggs,id', 'pack_id' => 'nullable|numeric|min:0', - 'custom_container' => 'nullable|string', 'startup' => 'nullable|string', - 'auto_deploy' => 'accepted', - 'custom_id' => 'numeric|unique:servers,id', 'skip_scripts' => 'boolean', ]; @@ -132,7 +120,6 @@ class Server extends Model implements CleansAttributes, ValidableContract */ protected $searchableColumns = [ 'name' => 10, - 'username' => 10, 'uuidShort' => 9, 'uuid' => 8, 'pack.name' => 7, diff --git a/app/Models/User.php b/app/Models/User.php index 9f063c8ed..7b09165aa 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -32,6 +32,9 @@ class User extends Model implements { use Authenticatable, Authorizable, CanResetPassword, Eloquence, Notifiable, Validable; + const USER_LEVEL_USER = 0; + const USER_LEVEL_ADMIN = 1; + /** * Level of servers to display when using access() on a user. * diff --git a/app/Repositories/Eloquent/DatabaseRepository.php b/app/Repositories/Eloquent/DatabaseRepository.php index de3ff65bf..f030812b5 100644 --- a/app/Repositories/Eloquent/DatabaseRepository.php +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -10,6 +10,7 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Database; +use Illuminate\Support\Collection; use Illuminate\Foundation\Application; use Illuminate\Database\DatabaseManager; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; @@ -17,6 +18,11 @@ use Pterodactyl\Exceptions\Repository\DuplicateDatabaseNameException; class DatabaseRepository extends EloquentRepository implements DatabaseRepositoryInterface { + /** + * @var string + */ + protected $connection = self::DEFAULT_CONNECTION_NAME; + /** * @var \Illuminate\Database\DatabaseManager */ @@ -45,6 +51,40 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor return Database::class; } + /** + * Set the connection name to execute statements against. + * + * @param string $connection + * @return $this + */ + public function setConnection(string $connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * Return the connection to execute statements aganist. + * + * @return string + */ + public function getConnection(): string + { + return $this->connection; + } + + /** + * Return all of the databases belonging to a server. + * + * @param int $server + * @return \Illuminate\Support\Collection + */ + public function getDatabasesForServer(int $server): Collection + { + return $this->getBuilder()->where('server_id', $server)->get($this->getColumns()); + } + /** * {@inheritdoc} * @return bool|\Illuminate\Database\Eloquent\Model @@ -67,80 +107,64 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor /** * {@inheritdoc} */ - public function createDatabase($database, $connection = null) + public function createDatabase($database) { - return $this->runStatement( - sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database), - $connection - ); + return $this->runStatement(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database)); } /** * {@inheritdoc} */ - public function createUser($username, $remote, $password, $connection = null) + public function createUser($username, $remote, $password) { - return $this->runStatement( - sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password), - $connection - ); + return $this->runStatement(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password)); } /** * {@inheritdoc} */ - public function assignUserToDatabase($database, $username, $remote, $connection = null) + public function assignUserToDatabase($database, $username, $remote) { - return $this->runStatement( - sprintf( - 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, EXECUTE ON `%s`.* TO `%s`@`%s`', - $database, - $username, - $remote - ), - $connection - ); + return $this->runStatement(sprintf( + 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, EXECUTE ON `%s`.* TO `%s`@`%s`', + $database, + $username, + $remote + )); } /** * {@inheritdoc} */ - public function flush($connection = null) + public function flush() { - return $this->runStatement('FLUSH PRIVILEGES', $connection); + return $this->runStatement('FLUSH PRIVILEGES'); } /** * {@inheritdoc} */ - public function dropDatabase($database, $connection = null) + public function dropDatabase($database) { - return $this->runStatement( - sprintf('DROP DATABASE IF EXISTS `%s`', $database), - $connection - ); + return $this->runStatement(sprintf('DROP DATABASE IF EXISTS `%s`', $database)); } /** * {@inheritdoc} */ - public function dropUser($username, $remote, $connection = null) + public function dropUser($username, $remote) { - return $this->runStatement( - sprintf('DROP USER IF EXISTS `%s`@`%s`', $username, $remote), - $connection - ); + return $this->runStatement(sprintf('DROP USER IF EXISTS `%s`@`%s`', $username, $remote)); } /** * Run the provided statement against the database on a given connection. * - * @param string $statement - * @param null|string $connection + * @param string $statement * @return bool */ - protected function runStatement($statement, $connection = null) + protected function runStatement($statement) { - return $this->database->connection($connection)->statement($statement); + return $this->database->connection($this->getConnection())->statement($statement); } } diff --git a/app/Repositories/Eloquent/EggVariableRepository.php b/app/Repositories/Eloquent/EggVariableRepository.php index 9fe1174b4..2c34c7527 100644 --- a/app/Repositories/Eloquent/EggVariableRepository.php +++ b/app/Repositories/Eloquent/EggVariableRepository.php @@ -9,6 +9,7 @@ namespace Pterodactyl\Repositories\Eloquent; +use Illuminate\Support\Collection; use Pterodactyl\Models\EggVariable; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; @@ -21,4 +22,20 @@ class EggVariableRepository extends EloquentRepository implements EggVariableRep { return EggVariable::class; } + + /** + * Return editable variables for a given egg. Editable variables must be set to + * user viewable in order to be picked up by this function. + * + * @param int $egg + * @return \Illuminate\Support\Collection + */ + public function getEditableVariables(int $egg): Collection + { + return $this->getBuilder()->where([ + ['egg_id', '=', $egg], + ['user_viewable', '=', 1], + ['user_editable', '=', 1], + ])->get($this->getColumns()); + } } diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index 10805fdea..ac023ff17 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -67,8 +67,8 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt Assert::integerish($id, 'First argument passed to findWithVariables must be integer, received %s.'); $instance = $this->getBuilder()->with('egg.variables', 'variables') - ->where($this->getModel()->getKeyName(), '=', $id) - ->first($this->getColumns()); + ->where($this->getModel()->getKeyName(), '=', $id) + ->first($this->getColumns()); if (is_null($instance)) { throw new RecordNotFoundException(); @@ -77,6 +77,36 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt return $instance; } + /** + * Get the primary allocation for a given server. If a model is passed into + * the function, load the allocation relationship onto it. Otherwise, find and + * return the server from the database. + * + * @param int|\Pterodactyl\Models\Server $server + * @param bool $refresh + * @return \Pterodactyl\Models\Server + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getPrimaryAllocation($server, bool $refresh = false): Server + { + $instance = $server; + if (! $instance instanceof Server) { + Assert::integerish($server, 'First argument passed to getPrimaryAllocation must be instance of \Pterodactyl\Models\Server or integer, received %s.'); + $instance = $this->getBuilder()->find($server, $this->getColumns()); + } + + if (! $instance) { + throw new RecordNotFoundException; + } + + if (! $instance->relationLoaded('allocation') || $refresh) { + $instance->load('allocation'); + } + + return $instance; + } + /** * {@inheritdoc} */ @@ -107,16 +137,21 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt } /** - * {@inheritdoc} + * Return enough data to be used for the creation of a server via the daemon. + * + * @param \Pterodactyl\Models\Server $server + * @param bool $refresh + * @return \Pterodactyl\Models\Server */ - public function getDataForCreation($id) + public function getDataForCreation(Server $server, bool $refresh = false): Server { - $instance = $this->getBuilder()->with(['allocation', 'allocations', 'pack', 'egg'])->find($id, $this->getColumns()); - if (! $instance) { - throw new RecordNotFoundException(); + foreach (['allocation', 'allocations', 'pack', 'egg'] as $relation) { + if (! $server->relationLoaded($relation) || $refresh) { + $server->load($relation); + } } - return $instance; + return $server; } /** diff --git a/app/Services/Allocations/SetDefaultAllocationService.php b/app/Services/Allocations/SetDefaultAllocationService.php new file mode 100644 index 000000000..66a858be3 --- /dev/null +++ b/app/Services/Allocations/SetDefaultAllocationService.php @@ -0,0 +1,110 @@ +connection = $connection; + $this->daemonRepository = $daemonRepository; + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Update the default allocation for a server only if that allocation is currently + * assigned to the specified server. + * + * @param int|\Pterodactyl\Models\Server $server + * @param int $allocation + * @return \Pterodactyl\Models\Allocation + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Allocation\AllocationDoesNotBelongToServerException + */ + public function handle($server, int $allocation): Allocation + { + if (! $server instanceof Server) { + $server = $this->serverRepository->find($server); + } + + $allocations = $this->repository->findWhere([['server_id', '=', $server->id]]); + $model = $allocations->filter(function ($model) use ($allocation) { + return $model->id === $allocation; + })->first(); + + if (! $model instanceof Allocation) { + throw new AllocationDoesNotBelongToServerException; + } + + $this->connection->beginTransaction(); + $this->serverRepository->withoutFresh()->update($server->id, ['allocation_id' => $model->id]); + + // Update on the daemon. + try { + $this->daemonRepository->setAccessServer($server->uuid)->setNode($server->node_id)->update([ + 'build' => [ + 'default' => [ + 'ip' => $model->ip, + 'port' => $model->port, + ], + 'ports|overwrite' => $allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(), + ], + ]); + + $this->connection->commit(); + } catch (RequestException $exception) { + $this->connection->rollBack(); + throw new DaemonConnectionException($exception); + } + + return $model; + } +} diff --git a/app/Services/Database/DatabaseHostService.php b/app/Services/Database/DatabaseHostService.php deleted file mode 100644 index cb7f1f9f3..000000000 --- a/app/Services/Database/DatabaseHostService.php +++ /dev/null @@ -1,148 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Database; - -use Illuminate\Database\DatabaseManager; -use Pterodactyl\Exceptions\DisplayException; -use Illuminate\Contracts\Encryption\Encrypter; -use Pterodactyl\Extensions\DynamicDatabaseConnection; -use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; -use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; - -class DatabaseHostService -{ - /** - * @var \Illuminate\Database\DatabaseManager - */ - protected $database; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface - */ - protected $databaseRepository; - - /** - * @var \Pterodactyl\Extensions\DynamicDatabaseConnection - */ - protected $dynamic; - - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - protected $encrypter; - - /** - * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface - */ - protected $repository; - - /** - * DatabaseHostService constructor. - * - * @param \Illuminate\Database\DatabaseManager $database - * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository - * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository - * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic - * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - */ - public function __construct( - DatabaseManager $database, - DatabaseRepositoryInterface $databaseRepository, - DatabaseHostRepositoryInterface $repository, - DynamicDatabaseConnection $dynamic, - Encrypter $encrypter - ) { - $this->database = $database; - $this->databaseRepository = $databaseRepository; - $this->dynamic = $dynamic; - $this->encrypter = $encrypter; - $this->repository = $repository; - } - - /** - * Create a new database host and persist it to the database. - * - * @param array $data - * @return \Pterodactyl\Models\DatabaseHost - * - * @throws \Throwable - * @throws \PDOException - */ - public function create(array $data) - { - $this->database->beginTransaction(); - - $host = $this->repository->create([ - 'password' => $this->encrypter->encrypt(array_get($data, 'password')), - 'name' => array_get($data, 'name'), - 'host' => array_get($data, 'host'), - 'port' => array_get($data, 'port'), - 'username' => array_get($data, 'username'), - 'max_databases' => null, - 'node_id' => array_get($data, 'node_id'), - ]); - - // Check Access - $this->dynamic->set('dynamic', $host); - $this->database->connection('dynamic')->select('SELECT 1 FROM dual'); - - $this->database->commit(); - - return $host; - } - - /** - * Update a database host and persist to the database. - * - * @param int $id - * @param array $data - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update($id, array $data) - { - $this->database->beginTransaction(); - - if (! empty(array_get($data, 'password'))) { - $data['password'] = $this->encrypter->encrypt($data['password']); - } else { - unset($data['password']); - } - - $host = $this->repository->update($id, $data); - - $this->dynamic->set('dynamic', $host); - $this->database->connection('dynamic')->select('SELECT 1 FROM dual'); - - $this->database->commit(); - - return $host; - } - - /** - * Delete a database host if it has no active databases attached to it. - * - * @param int $id - * @return bool|null - * - * @throws \Pterodactyl\Exceptions\DisplayException - */ - public function delete($id) - { - $count = $this->databaseRepository->findCountWhere([['database_host_id', '=', $id]]); - if ($count > 0) { - throw new DisplayException(trans('exceptions.databases.delete_has_databases')); - } - - return $this->repository->delete($id); - } -} diff --git a/app/Services/Database/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php similarity index 67% rename from app/Services/Database/DatabaseManagementService.php rename to app/Services/Databases/DatabaseManagementService.php index 21d01809e..95182a288 100644 --- a/app/Services/Database/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -7,8 +7,9 @@ * https://opensource.org/licenses/MIT */ -namespace Pterodactyl\Services\Database; +namespace Pterodactyl\Services\Databases; +use Pterodactyl\Models\Database; use Illuminate\Database\DatabaseManager; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Extensions\DynamicDatabaseConnection; @@ -79,28 +80,26 @@ class DatabaseManagementService $database = $this->repository->createIfNotExists($data); $this->dynamic->set('dynamic', $data['database_host_id']); - $this->repository->createDatabase($database->database, 'dynamic'); + $this->repository->createDatabase($database->database); $this->repository->createUser( $database->username, $database->remote, - $this->encrypter->decrypt($database->password), - 'dynamic' + $this->encrypter->decrypt($database->password) ); $this->repository->assignUserToDatabase( $database->database, $database->username, - $database->remote, - 'dynamic' + $database->remote ); - $this->repository->flush('dynamic'); + $this->repository->flush(); $this->database->commit(); } catch (\Exception $ex) { try { - if (isset($database)) { - $this->repository->dropDatabase($database->database, 'dynamic'); - $this->repository->dropUser($database->username, $database->remote, 'dynamic'); - $this->repository->flush('dynamic'); + if (isset($database) && $database instanceof Database) { + $this->repository->dropDatabase($database->database); + $this->repository->dropUser($database->username, $database->remote); + $this->repository->flush(); } } catch (\Exception $exTwo) { // ignore an exception @@ -113,62 +112,22 @@ class DatabaseManagementService return $database; } - /** - * Change the password for a specific user and database combination. - * - * @param int $id - * @param string $password - * @return bool - * - * @throws \Exception - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function changePassword($id, $password) - { - $database = $this->repository->find($id); - $this->dynamic->set('dynamic', $database->database_host_id); - - $this->database->beginTransaction(); - - try { - $updated = $this->repository->withoutFresh()->update($id, [ - 'password' => $this->encrypter->encrypt($password), - ]); - - $this->repository->dropUser($database->username, $database->remote, 'dynamic'); - $this->repository->createUser($database->username, $database->remote, $password, 'dynamic'); - $this->repository->assignUserToDatabase( - $database->database, - $database->username, - $database->remote, - 'dynamic' - ); - $this->repository->flush('dynamic'); - - $this->database->commit(); - } catch (\Exception $ex) { - $this->database->rollBack(); - throw $ex; - } - - return $updated; - } - /** * Delete a database from the given host server. * * @param int $id * @return bool|null + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function delete($id) { $database = $this->repository->find($id); $this->dynamic->set('dynamic', $database->database_host_id); - $this->repository->dropDatabase($database->database, 'dynamic'); - $this->repository->dropUser($database->username, $database->remote, 'dynamic'); - $this->repository->flush('dynamic'); + $this->repository->dropDatabase($database->database); + $this->repository->dropUser($database->username, $database->remote); + $this->repository->flush(); return $this->repository->delete($id); } diff --git a/app/Services/Databases/DatabasePasswordService.php b/app/Services/Databases/DatabasePasswordService.php new file mode 100644 index 000000000..4f6443ced --- /dev/null +++ b/app/Services/Databases/DatabasePasswordService.php @@ -0,0 +1,86 @@ +connection = $connection; + $this->dynamic = $dynamic; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Updates a password for a given database. + * + * @param \Pterodactyl\Models\Database|int $database + * @param string $password + * @return bool + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($database, string $password): bool + { + if (! $database instanceof Database) { + $database = $this->repository->find($database); + } + + $this->dynamic->set('dynamic', $database->database_host_id); + $this->connection->beginTransaction(); + + $updated = $this->repository->withoutFresh()->update($database->id, [ + 'password' => $this->encrypter->encrypt($password), + ]); + + $this->repository->dropUser($database->username, $database->remote); + $this->repository->createUser($database->username, $database->remote, $password); + $this->repository->assignUserToDatabase($database->database, $database->username, $database->remote); + $this->repository->flush(); + + unset($password); + $this->connection->commit(); + + return $updated; + } +} diff --git a/app/Services/Databases/Hosts/HostCreationService.php b/app/Services/Databases/Hosts/HostCreationService.php new file mode 100644 index 000000000..15b32ea04 --- /dev/null +++ b/app/Services/Databases/Hosts/HostCreationService.php @@ -0,0 +1,92 @@ +connection = $connection; + $this->databaseManager = $databaseManager; + $this->dynamic = $dynamic; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Create a new database host on the Panel. + * + * @param array $data + * @return \Pterodactyl\Models\DatabaseHost + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(array $data): DatabaseHost + { + $this->connection->beginTransaction(); + + $host = $this->repository->create([ + 'password' => $this->encrypter->encrypt(array_get($data, 'password')), + 'name' => array_get($data, 'name'), + 'host' => array_get($data, 'host'), + 'port' => array_get($data, 'port'), + 'username' => array_get($data, 'username'), + 'max_databases' => null, + 'node_id' => array_get($data, 'node_id'), + ]); + + // Confirm access using the provided credentials before saving data. + $this->dynamic->set('dynamic', $host); + $this->databaseManager->connection('dynamic')->select('SELECT 1 FROM dual'); + $this->connection->commit(); + + return $host; + } +} diff --git a/app/Services/Databases/Hosts/HostDeletionService.php b/app/Services/Databases/Hosts/HostDeletionService.php new file mode 100644 index 000000000..b69c8dcf9 --- /dev/null +++ b/app/Services/Databases/Hosts/HostDeletionService.php @@ -0,0 +1,53 @@ +databaseRepository = $databaseRepository; + $this->repository = $repository; + } + + /** + * Delete a specified host from the Panel if no databases are + * attached to it. + * + * @param int $host + * @return int + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function handle(int $host): int + { + $count = $this->databaseRepository->findCountWhere([['database_host_id', '=', $host]]); + if ($count > 0) { + throw new HasActiveServersException(trans('exceptions.databases.delete_has_databases')); + } + + return $this->repository->delete($host); + } +} diff --git a/app/Services/Databases/Hosts/HostUpdateService.php b/app/Services/Databases/Hosts/HostUpdateService.php new file mode 100644 index 000000000..5f4b19b31 --- /dev/null +++ b/app/Services/Databases/Hosts/HostUpdateService.php @@ -0,0 +1,96 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Databases\Hosts; + +use Pterodactyl\Models\DatabaseHost; +use Illuminate\Database\DatabaseManager; +use Illuminate\Database\ConnectionInterface; +use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Extensions\DynamicDatabaseConnection; +use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; + +class HostUpdateService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + private $connection; + + /** + * @var \Illuminate\Database\DatabaseManager + */ + private $databaseManager; + + /** + * @var \Pterodactyl\Extensions\DynamicDatabaseConnection + */ + private $dynamic; + + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + private $encrypter; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface + */ + private $repository; + + /** + * DatabaseHostService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\DatabaseManager $databaseManager + * @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository + * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter + */ + public function __construct( + ConnectionInterface $connection, + DatabaseManager $databaseManager, + DatabaseHostRepositoryInterface $repository, + DynamicDatabaseConnection $dynamic, + Encrypter $encrypter + ) { + $this->connection = $connection; + $this->databaseManager = $databaseManager; + $this->dynamic = $dynamic; + $this->encrypter = $encrypter; + $this->repository = $repository; + } + + /** + * Update a database host and persist to the database. + * + * @param int $hostId + * @param array $data + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(int $hostId, array $data): DatabaseHost + { + if (! empty(array_get($data, 'password'))) { + $data['password'] = $this->encrypter->encrypt($data['password']); + } else { + unset($data['password']); + } + + $this->connection->beginTransaction(); + $host = $this->repository->update($hostId, $data); + + $this->dynamic->set('dynamic', $host); + $this->databaseManager->connection('dynamic')->select('SELECT 1 FROM dual'); + $this->connection->commit(); + + return $host; + } +} diff --git a/app/Services/Servers/EnvironmentService.php b/app/Services/Servers/EnvironmentService.php index 987d90b2d..177034e10 100644 --- a/app/Services/Servers/EnvironmentService.php +++ b/app/Services/Servers/EnvironmentService.php @@ -1,42 +1,37 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Servers; use Pterodactyl\Models\Server; +use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class EnvironmentService { - const ENVIRONMENT_CASTS = [ - 'STARTUP' => 'startup', - 'P_SERVER_LOCATION' => 'location.short', - 'P_SERVER_UUID' => 'uuid', - ]; - /** * @var array */ - protected $additional = []; + private $additional = []; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + private $config; /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface */ - protected $repository; + private $repository; /** * EnvironmentService constructor. * + * @param \Illuminate\Contracts\Config\Repository $config * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository */ - public function __construct(ServerRepositoryInterface $repository) + public function __construct(ConfigRepository $config, ServerRepositoryInterface $repository) { + $this->config = $config; $this->repository = $repository; } @@ -46,42 +41,70 @@ class EnvironmentService * * @param string $key * @param callable $closure - * @return $this */ - public function setEnvironmentKey($key, callable $closure) + public function setEnvironmentKey(string $key, callable $closure) { - $this->additional[] = [$key, $closure]; + $this->additional[$key] = $closure; + } - return $this; + /** + * Return the dynamically added additional keys. + * + * @return array + */ + public function getEnvironmentKeys(): array + { + return $this->additional; } /** * Take all of the environment variables configured for this server and return * them in an easy to process format. * - * @param int|\Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Server $server * @return array * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function process($server) + public function handle(Server $server): array { - if (! $server instanceof Server) { - $server = $this->repository->find($server); - } - $variables = $this->repository->getVariablesWithValues($server->id); - // Process static environment variables defined in this file. - foreach (self::ENVIRONMENT_CASTS as $key => $object) { + // Process environment variables defined in this file. This is done first + // in order to allow run-time and config defined variables to take + // priority over built-in values. + foreach ($this->getEnvironmentMappings() as $key => $object) { $variables[$key] = object_get($server, $object); } + // Process variables set in the configuration file. + foreach ($this->config->get('pterodactyl.environment_mappings', []) as $key => $object) { + if (is_callable($object)) { + $variables[$key] = call_user_func($object, $server); + } else { + $variables[$key] = object_get($server, $object); + } + } + // Process dynamically included environment variables. - foreach ($this->additional as $item) { - $variables[$item[0]] = call_user_func($item[1], $server); + foreach ($this->additional as $key => $closure) { + $variables[$key] = call_user_func($closure, $server); } return $variables; } + + /** + * Return a mapping of Panel default environment variables. + * + * @return array + */ + final private function getEnvironmentMappings(): array + { + return [ + 'STARTUP' => 'startup', + 'P_SERVER_LOCATION' => 'location.short', + 'P_SERVER_UUID' => 'uuid', + ]; + } } diff --git a/app/Services/Servers/ServerAccessHelperService.php b/app/Services/Servers/ServerAccessHelperService.php deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index b92191711..d91d9db95 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -19,12 +19,12 @@ class ServerConfigurationStructureService /** * @var \Pterodactyl\Services\Servers\EnvironmentService */ - protected $environment; + private $environment; /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface */ - protected $repository; + private $repository; /** * ServerConfigurationStructureService constructor. @@ -41,19 +41,21 @@ class ServerConfigurationStructureService } /** - * @param int|\Pterodactyl\Models\Server $server + * Return a configuration array for a specific server when passed a server model. + * + * @param \Pterodactyl\Models\Server $server * @return array + * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($server): array + public function handle(Server $server): array { - if (! $server instanceof Server || array_diff(self::REQUIRED_RELATIONS, $server->getRelations())) { - $server = $this->repository->getDataForCreation(is_digit($server) ? $server : $server->id); + if (array_diff(self::REQUIRED_RELATIONS, $server->getRelations())) { + $server = $this->repository->getDataForCreation($server); } return [ 'uuid' => $server->uuid, - 'user' => $server->username, 'build' => [ 'default' => [ 'ip' => $server->allocation->ip, @@ -62,7 +64,7 @@ class ServerConfigurationStructureService 'ports' => $server->allocations->groupBy('ip')->map(function ($item) { return $item->pluck('port'); })->toArray(), - 'env' => $this->environment->process($server), + 'env' => $this->environment->handle($server), 'memory' => (int) $server->memory, 'swap' => (int) $server->swap, 'io' => (int) $server->io, @@ -70,7 +72,6 @@ class ServerConfigurationStructureService 'disk' => (int) $server->disk, 'image' => $server->image, ], - 'keys' => [], 'service' => [ 'egg' => $server->egg->uuid, 'pack' => object_get($server, 'pack.uuid'), diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index 0994abe55..86e580a22 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -1,18 +1,12 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Servers; use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\Node; +use Pterodactyl\Models\User; use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Services\Nodes\NodeCreationService; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; @@ -26,52 +20,47 @@ class ServerCreationService /** * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface */ - protected $allocationRepository; + private $allocationRepository; /** * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService */ - protected $configurationStructureService; + private $configurationStructureService; /** * @var \Illuminate\Database\ConnectionInterface */ - protected $connection; + private $connection; /** * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface */ - protected $daemonServerRepository; + private $daemonServerRepository; /** * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface */ - protected $nodeRepository; + private $nodeRepository; /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface */ - protected $repository; + private $repository; /** * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface */ - protected $serverVariableRepository; + private $serverVariableRepository; /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface */ - protected $userRepository; - - /** - * @var \Pterodactyl\Services\Servers\UsernameGenerationService - */ - protected $usernameService; + private $userRepository; /** * @var \Pterodactyl\Services\Servers\VariableValidatorService */ - protected $validatorService; + private $validatorService; /** * CreationService constructor. @@ -84,7 +73,6 @@ class ServerCreationService * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository - * @param \Pterodactyl\Services\Servers\UsernameGenerationService $usernameService * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService */ public function __construct( @@ -96,7 +84,6 @@ class ServerCreationService ServerRepositoryInterface $repository, ServerVariableRepositoryInterface $serverVariableRepository, UserRepositoryInterface $userRepository, - UsernameGenerationService $usernameService, VariableValidatorService $validatorService ) { $this->allocationRepository = $allocationRepository; @@ -107,7 +94,6 @@ class ServerCreationService $this->repository = $repository; $this->serverVariableRepository = $serverVariableRepository; $this->userRepository = $userRepository; - $this->usernameService = $usernameService; $this->validatorService = $validatorService; } @@ -124,35 +110,30 @@ class ServerCreationService public function create(array $data) { // @todo auto-deployment - $validator = $this->validatorService->isAdmin()->setFields($data['environment'])->validate($data['egg_id']); - $uniqueShort = str_random(8); $this->connection->beginTransaction(); - $server = $this->repository->create([ 'uuid' => Uuid::uuid4()->toString(), - 'uuidShort' => $uniqueShort, - 'node_id' => $data['node_id'], - 'name' => $data['name'], - 'description' => $data['description'], + 'uuidShort' => str_random(8), + 'node_id' => array_get($data, 'node_id'), + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description'), 'skip_scripts' => isset($data['skip_scripts']), 'suspended' => false, - 'owner_id' => $data['owner_id'], - 'memory' => $data['memory'], - 'swap' => $data['swap'], - 'disk' => $data['disk'], - 'io' => $data['io'], - 'cpu' => $data['cpu'], + 'owner_id' => array_get($data, 'owner_id'), + 'memory' => array_get($data, 'memory'), + 'swap' => array_get($data, 'swap'), + 'disk' => array_get($data, 'disk'), + 'io' => array_get($data, 'io'), + 'cpu' => array_get($data, 'cpu'), 'oom_disabled' => isset($data['oom_disabled']), - 'allocation_id' => $data['allocation_id'], - 'nest_id' => $data['nest_id'], - 'egg_id' => $data['egg_id'], + 'allocation_id' => array_get($data, 'allocation_id'), + 'nest_id' => array_get($data, 'nest_id'), + 'egg_id' => array_get($data, 'egg_id'), 'pack_id' => (! isset($data['pack_id']) || $data['pack_id'] == 0) ? null : $data['pack_id'], - 'startup' => $data['startup'], - 'daemonSecret' => str_random(NodeCreationService::DAEMON_SECRET_LENGTH), - 'image' => $data['docker_image'], - 'username' => $this->usernameService->generate($data['name'], $uniqueShort), - 'sftp_password' => null, + 'startup' => array_get($data, 'startup'), + 'daemonSecret' => str_random(Node::DAEMON_SECRET_LENGTH), + 'image' => array_get($data, 'docker_image'), ]); // Process allocations and assign them to the server in the database. @@ -164,17 +145,21 @@ class ServerCreationService $this->allocationRepository->assignAllocationsToServer($server->id, $records); // Process the passed variables and store them in the database. - $records = []; - foreach ($validator->getResults() as $result) { - $records[] = [ - 'server_id' => $server->id, - 'variable_id' => $result['id'], - 'variable_value' => $result['value'], - ]; - } + $this->validatorService->setUserLevel(User::USER_LEVEL_ADMIN); + $results = $this->validatorService->handle(array_get($data, 'egg_id'), array_get($data, 'environment', [])); - $this->serverVariableRepository->insert($records); - $structure = $this->configurationStructureService->handle($server->id); + $records = $results->map(function ($result) use ($server) { + return [ + 'server_id' => $server->id, + 'variable_id' => $result->id, + 'variable_value' => $result->value, + ]; + })->toArray(); + + if (! empty($records)) { + $this->serverVariableRepository->insert($records); + } + $structure = $this->configurationStructureService->handle($server); // Create the server on the daemon & commit it to the database. try { diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php index 836df7e4f..1129a187c 100644 --- a/app/Services/Servers/ServerDeletionService.php +++ b/app/Services/Servers/ServerDeletionService.php @@ -14,7 +14,7 @@ use Pterodactyl\Models\Server; use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Services\Database\DatabaseManagementService; +use Pterodactyl\Services\Databases\DatabaseManagementService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; @@ -32,7 +32,7 @@ class ServerDeletionService protected $daemonServerRepository; /** - * @var \Pterodactyl\Services\Database\DatabaseManagementService + * @var \Pterodactyl\Services\Databases\DatabaseManagementService */ protected $databaseManagementService; @@ -62,7 +62,7 @@ class ServerDeletionService * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository - * @param \Pterodactyl\Services\Database\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository * @param \Illuminate\Log\Writer $writer */ diff --git a/app/Services/Servers/StartupCommandViewService.php b/app/Services/Servers/StartupCommandViewService.php new file mode 100644 index 000000000..14a4cc3c3 --- /dev/null +++ b/app/Services/Servers/StartupCommandViewService.php @@ -0,0 +1,55 @@ +repository = $repository; + } + + /** + * Generate a startup command for a server and return all of the user-viewable variables + * as well as thier assigned values. + * + * @param int $server + * @return \Illuminate\Support\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(int $server): Collection + { + $response = $this->repository->getVariablesWithValues($server, true); + $server = $this->repository->getPrimaryAllocation($response->server); + + $find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}']; + $replace = [$server->memory, $server->allocation->ip, $server->allocation->port]; + + $variables = $server->egg->variables->each(function ($variable) use (&$find, &$replace, $response) { + $find[] = '{{' . $variable->env_variable . '}}'; + $replace[] = $variable->user_viewable ? $response->data[$variable->env_variable] : '[hidden]'; + })->filter(function ($variable) { + return $variable->user_viewable === 1; + }); + + return collect([ + 'startup' => str_replace($find, $replace, $server->startup), + 'variables' => $variables, + 'server_values' => $response->data, + ]); + } +} diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index 25650b1c0..ae91fb3ba 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -1,58 +1,50 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Servers; +use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use GuzzleHttp\Exception\RequestException; use Illuminate\Database\ConnectionInterface; -use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Traits\Services\HasUserLevels; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class StartupModificationService { - /** - * @var bool - */ - protected $admin = false; + use HasUserLevels; /** * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface */ - protected $daemonServerRepository; + private $daemonServerRepository; /** * @var \Illuminate\Database\ConnectionInterface */ - protected $connection; + private $connection; /** * @var \Pterodactyl\Services\Servers\EnvironmentService */ - protected $environmentService; + private $environmentService; /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface */ - protected $repository; + private $repository; /** * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface */ - protected $serverVariableRepository; + private $serverVariableRepository; /** * @var \Pterodactyl\Services\Servers\VariableValidatorService */ - protected $validatorService; + private $validatorService; /** * StartupModificationService constructor. @@ -80,91 +72,81 @@ class StartupModificationService $this->validatorService = $validatorService; } - /** - * Determine if this function should run at an administrative level. - * - * @param bool $bool - * @return $this - */ - public function isAdmin($bool = true) - { - $this->admin = $bool; - - return $this; - } - /** * Process startup modification for a server. * - * @param int|\Pterodactyl\Models\Server $server - * @param array $data + * @param \Pterodactyl\Models\Server $server + * @param array $data * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($server, array $data) + public function handle(Server $server, array $data) { - if (! $server instanceof Server) { - $server = $this->repository->find($server); + $this->connection->beginTransaction(); + if (! is_null(array_get($data, 'environment'))) { + $this->validatorService->setUserLevel($this->getUserLevel()); + $results = $this->validatorService->handle(array_get($data, 'egg_id', $server->egg_id), array_get($data, 'environment', [])); + + $results->each(function ($result) use ($server) { + $this->serverVariableRepository->withoutFresh()->updateOrCreate([ + 'server_id' => $server->id, + 'variable_id' => $result->id, + ], [ + 'variable_value' => $result->value, + ]); + }); } + $daemonData = ['build' => [ + 'env|overwrite' => $this->environmentService->handle($server), + ]]; + + if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { + $this->updateAdministrativeSettings($data, $server, $daemonData); + } + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update($daemonData); + } catch (RequestException $exception) { + $this->connection->rollBack(); + throw new DaemonConnectionException($exception); + } + + $this->connection->commit(); + } + + /** + * Update certain administrative settings for a server in the DB. + * + * @param array $data + * @param \Pterodactyl\Models\Server $server + * @param array $daemonData + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + private function updateAdministrativeSettings(array $data, Server &$server, array &$daemonData) + { + $server = $this->repository->update($server->id, [ + 'installed' => 0, + 'startup' => array_get($data, 'startup', $server->startup), + 'nest_id' => array_get($data, 'nest_id', $server->nest_id), + 'egg_id' => array_get($data, 'egg_id', $server->egg_id), + 'pack_id' => array_get($data, 'pack_id', $server->pack_id) > 0 ? array_get($data, 'pack_id', $server->pack_id) : null, + 'skip_scripts' => isset($data['skip_scripts']), + ]); + if ( $server->nest_id != array_get($data, 'nest_id', $server->nest_id) || $server->egg_id != array_get($data, 'egg_id', $server->egg_id) || $server->pack_id != array_get($data, 'pack_id', $server->pack_id) ) { - $hasServiceChanges = true; - } - - $this->connection->beginTransaction(); - if (isset($data['environment'])) { - $validator = $this->validatorService->isAdmin($this->admin) - ->setFields($data['environment']) - ->validate(array_get($data, 'egg_id', $server->egg_id)); - - foreach ($validator->getResults() as $result) { - $this->serverVariableRepository->withoutFresh()->updateOrCreate([ - 'server_id' => $server->id, - 'variable_id' => $result['id'], - ], [ - 'variable_value' => $result['value'], - ]); - } - } - - $daemonData = [ - 'build' => [ - 'env|overwrite' => $this->environmentService->process($server), - ], - ]; - - if ($this->admin) { - $server = $this->repository->update($server->id, [ - 'installed' => 0, - 'startup' => array_get($data, 'startup', $server->startup), - 'nest_id' => array_get($data, 'nest_id', $server->nest_id), - 'egg_id' => array_get($data, 'egg_id', $server->egg_id), - 'pack_id' => array_get($data, 'pack_id', $server->pack_id) > 0 ? array_get($data, 'pack_id', $server->pack_id) : null, - 'skip_scripts' => isset($data['skip_scripts']), - ]); - - if (isset($hasServiceChanges)) { - $daemonData['service'] = array_merge( - $this->repository->withColumns(['id', 'egg_id', 'pack_id'])->getDaemonServiceData($server->id), - ['skip_scripts' => isset($data['skip_scripts'])] - ); - } - } - - try { - $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update($daemonData); - $this->connection->commit(); - } catch (RequestException $exception) { - $response = $exception->getResponse(); - throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ]), $exception, 'warning'); + $daemonData['service'] = array_merge( + $this->repository->withColumns(['id', 'egg_id', 'pack_id'])->getDaemonServiceData($server->id), + ['skip_scripts' => isset($data['skip_scripts'])] + ); } } } diff --git a/app/Services/Servers/UsernameGenerationService.php b/app/Services/Servers/UsernameGenerationService.php deleted file mode 100644 index 3fb2c6d3b..000000000 --- a/app/Services/Servers/UsernameGenerationService.php +++ /dev/null @@ -1,40 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Servers; - -class UsernameGenerationService -{ - /** - * Generate a unique username to be used for SFTP connections and identification - * of the server docker container on the host system. - * - * @param string $name - * @param null $identifier - * @return string - */ - public function generate($name, $identifier = null) - { - if (is_null($identifier) || ! ctype_alnum($identifier)) { - $unique = str_random(8); - } else { - if (strlen($identifier) < 8) { - $unique = $identifier . str_random((8 - strlen($identifier))); - } else { - $unique = substr($identifier, 0, 8); - } - } - - // Filter the Server Name - $name = trim(preg_replace('/[^A-Za-z0-9]+/', '', $name), '_'); - $name = (strlen($name) < 1) ? str_random(6) : $name; - - return strtolower(substr($name, 0, 6) . '_' . $unique); - } -} diff --git a/app/Services/Servers/VariableValidatorService.php b/app/Services/Servers/VariableValidatorService.php index 7340d2f7b..b4f722c79 100644 --- a/app/Services/Servers/VariableValidatorService.php +++ b/app/Services/Servers/VariableValidatorService.php @@ -9,6 +9,9 @@ namespace Pterodactyl\Services\Servers; +use Pterodactyl\Models\User; +use Illuminate\Support\Collection; +use Pterodactyl\Traits\Services\HasUserLevels; use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Illuminate\Contracts\Validation\Factory as ValidationFactory; @@ -17,20 +20,7 @@ use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; class VariableValidatorService { - /** - * @var bool - */ - protected $isAdmin = false; - - /** - * @var array - */ - protected $fields = []; - - /** - * @var array - */ - protected $results = []; + use HasUserLevels; /** * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface @@ -72,56 +62,26 @@ class VariableValidatorService $this->validator = $validator; } - /** - * Set the fields with populated data to validate. - * - * @param array $fields - * @return $this - */ - public function setFields(array $fields) - { - $this->fields = $fields; - - return $this; - } - - /** - * Set this function to be running at the administrative level. - * - * @param bool $bool - * @return $this - */ - public function isAdmin($bool = true) - { - $this->isAdmin = $bool; - - return $this; - } - /** * Validate all of the passed data aganist the given service option variables. * - * @param int $option - * @return $this + * @param int $egg + * @param array $fields + * @return \Illuminate\Support\Collection */ - public function validate($option) + public function handle(int $egg, array $fields = []): Collection { - $variables = $this->optionVariableRepository->findWhere([['egg_id', '=', $option]]); - if (count($variables) === 0) { - $this->results = []; + $variables = $this->optionVariableRepository->findWhere([['egg_id', '=', $egg]]); - return $this; - } - - $variables->each(function ($item) { - // Skip doing anything if user is not an admin and variable is not user viewable - // or editable. - if (! $this->isAdmin && (! $item->user_editable || ! $item->user_viewable)) { - return; + return $variables->map(function ($item) use ($fields) { + // Skip doing anything if user is not an admin and + // variable is not user viewable or editable. + if (! $this->isUserLevel(User::USER_LEVEL_ADMIN) && (! $item->user_editable || ! $item->user_viewable)) { + return false; } $validator = $this->validator->make([ - 'variable_value' => array_key_exists($item->env_variable, $this->fields) ? $this->fields[$item->env_variable] : null, + 'variable_value' => array_get($fields, $item->env_variable), ], [ 'variable_value' => $item->rules, ]); @@ -136,23 +96,13 @@ class VariableValidatorService )); } - $this->results[] = [ + return (object) [ 'id' => $item->id, 'key' => $item->env_variable, - 'value' => $this->fields[$item->env_variable], + 'value' => array_get($fields, $item->env_variable), ]; + })->filter(function ($item) { + return is_object($item); }); - - return $this; - } - - /** - * Return the final results after everything has been validated. - * - * @return array - */ - public function getResults() - { - return $this->results; } } diff --git a/app/Services/Sftp/AuthenticateUsingPasswordService.php b/app/Services/Sftp/AuthenticateUsingPasswordService.php new file mode 100644 index 000000000..487d251d4 --- /dev/null +++ b/app/Services/Sftp/AuthenticateUsingPasswordService.php @@ -0,0 +1,90 @@ +keyProviderService = $keyProviderService; + $this->repository = $repository; + $this->userRepository = $userRepository; + } + + /** + * Attempt to authenticate a provded username and password and determine if they + * have permission to access a given server. This function does not account for + * subusers currently. Only administrators and server owners can login to access + * their files at this time. + * + * Server must exist on the node that the API call is being made from in order for a + * valid response to be provided. + * + * @param string $username + * @param string $password + * @param string|null $server + * @param int $node + * @return array + * + * @throws \Illuminate\Auth\AuthenticationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(string $username, string $password, int $node, string $server = null): array + { + if (is_null($server)) { + throw new RecordNotFoundException; + } + + try { + $user = $this->userRepository->withColumns(['id', 'root_admin', 'password'])->findFirstWhere([['username', '=', $username]]); + + if (! password_verify($password, $user->password)) { + throw new AuthenticationException; + } + } catch (RecordNotFoundException $exception) { + throw new AuthenticationException; + } + + $server = $this->repository->withColumns(['id', 'node_id', 'owner_id', 'uuid'])->getByUuid($server); + if ($server->node_id !== $node || (! $user->root_admin && $server->owner_id !== $user->id)) { + throw new RecordNotFoundException; + } + + return [ + 'server' => $server->uuid, + 'token' => $this->keyProviderService->handle($server->id, $user->id), + ]; + } +} diff --git a/app/Traits/Controllers/JavascriptInjection.php b/app/Traits/Controllers/JavascriptInjection.php index 5063a50f2..c6efc86ac 100644 --- a/app/Traits/Controllers/JavascriptInjection.php +++ b/app/Traits/Controllers/JavascriptInjection.php @@ -10,32 +10,46 @@ namespace Pterodactyl\Traits\Controllers; use Javascript; +use Illuminate\Http\Request; trait JavascriptInjection { /** - * @var \Illuminate\Contracts\Session\Session + * @var \Illuminate\Http\Request */ - protected $session; + private $request; + + /** + * Set the request object to use when injecting JS. + * + * @param \Illuminate\Http\Request $request + * @return $this + */ + public function setRequest(Request $request) + { + $this->request = $request; + + return $this; + } /** * Injects server javascript into the page to be used by other services. * * @param array $args * @param bool $overwrite - * @return mixed + * @return array */ public function injectJavascript($args = [], $overwrite = false) { - $server = $this->session->get('server_data.model'); - $token = $this->session->get('server_data.token'); + $request = $this->request ?? app()->make(Request::class); + $server = $request->attributes->get('server'); + $token = $request->attributes->get('server_token'); $response = array_merge([ 'server' => [ 'uuid' => $server->uuid, 'uuidShort' => $server->uuidShort, 'daemonSecret' => $token, - 'username' => $server->username, ], 'node' => [ 'fqdn' => $server->node->fqdn, diff --git a/app/Traits/Services/HasUserLevels.php b/app/Traits/Services/HasUserLevels.php new file mode 100644 index 000000000..d2d95e233 --- /dev/null +++ b/app/Traits/Services/HasUserLevels.php @@ -0,0 +1,44 @@ +userLevel = $level; + } + + /** + * Determine which level this function is running at. + * + * @return int + */ + public function getUserLevel(): int + { + return $this->userLevel; + } + + /** + * Determine if the current user level is set to a specific level. + * + * @param int $level + * @return bool + */ + public function isUserLevel(int $level): bool + { + return $this->getUserLevel() === $level; + } +} diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 2bf1b2c8a..f232d8a1e 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -184,4 +184,19 @@ return [ 'daemon/*', 'remote/*', ], + + /* + |-------------------------------------------------------------------------- + | Dynamic Environment Variables + |-------------------------------------------------------------------------- + | + | Place dynamic environment variables here that should be auto-appended + | to server environment fields when the server is created or updated. + | + | Items should be in 'key' => 'value' format, where key is the environment + | variable name, and value is the server-object key. For example: + | + | 'P_SERVER_CREATED_AT' => 'created_at' + */ + 'environment_variables' => [], ]; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index f3e4f4093..54ba984be 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -29,9 +29,10 @@ $factory->define(Pterodactyl\Models\Server::class, function (Faker\Generator $fa 'io' => 500, 'cpu' => 0, 'oom_disabled' => 0, + 'allocation_id' => $faker->randomNumber(), + 'nest_id' => $faker->randomNumber(), + 'egg_id' => $faker->randomNumber(), 'pack_id' => null, - 'username' => $faker->userName, - 'sftp_password' => null, 'installed' => 1, 'created_at' => \Carbon\Carbon::now(), 'updated_at' => \Carbon\Carbon::now(), @@ -39,6 +40,8 @@ $factory->define(Pterodactyl\Models\Server::class, function (Faker\Generator $fa }); $factory->define(Pterodactyl\Models\User::class, function (Faker\Generator $faker) { + static $password; + return [ 'id' => $faker->unique()->randomNumber(), 'external_id' => null, @@ -47,7 +50,7 @@ $factory->define(Pterodactyl\Models\User::class, function (Faker\Generator $fake 'email' => $faker->safeEmail, 'name_first' => $faker->firstName, 'name_last' => $faker->lastName, - 'password' => bcrypt('password'), + 'password' => $password ?: $password = bcrypt('password'), 'language' => 'en', 'root_admin' => false, 'use_totp' => false, @@ -173,6 +176,21 @@ $factory->define(Pterodactyl\Models\DatabaseHost::class, function (Faker\Generat ]; }); +$factory->define(Pterodactyl\Models\Database::class, function (Faker\Generator $faker) { + static $password; + + return [ + 'id' => $faker->unique()->randomNumber(), + 'server_id' => $faker->randomNumber(), + 'database_host_id' => $faker->randomNumber(), + 'database' => str_random(10), + 'username' => str_random(10), + 'password' => $password ?: bcrypt('test123'), + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString(), + ]; +}); + $factory->define(Pterodactyl\Models\Schedule::class, function (Faker\Generator $faker) { return [ 'id' => $faker->unique()->randomNumber(), diff --git a/database/migrations/2017_10_24_222238_RemoveLegacySFTPInformation.php b/database/migrations/2017_10_24_222238_RemoveLegacySFTPInformation.php new file mode 100644 index 000000000..e41acd275 --- /dev/null +++ b/database/migrations/2017_10_24_222238_RemoveLegacySFTPInformation.php @@ -0,0 +1,32 @@ +dropUnique(['username']); + + $table->dropColumn('username'); + $table->dropColumn('sftp_password'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->string('username')->nullable()->after('image')->unique(); + $table->text('sftp_password')->after('image'); + }); + } +} diff --git a/public/js/laroute.js b/public/js/laroute.js index e6e9db4c7..deec07500 100644 --- a/public/js/laroute.js +++ b/public/js/laroute.js @@ -6,7 +6,7 @@ absolute: false, rootUrl: 'http://pterodactyl.app', - routes : [{"host":null,"methods":["GET","HEAD"],"uri":"api\/user","name":"api.user","action":"Pterodactyl\Http\Controllers\API\User\CoreController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/user\/server\/{server}","name":"api.user.server","action":"Pterodactyl\Http\Controllers\API\User\ServerController@index"},{"host":null,"methods":["POST"],"uri":"api\/user\/server\/{server}\/power","name":"api.user.server.power","action":"Pterodactyl\Http\Controllers\API\User\ServerController@power"},{"host":null,"methods":["POST"],"uri":"api\/user\/server\/{server}\/command","name":"api.user.server.command","action":"Pterodactyl\Http\Controllers\API\User\ServerController@command"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\CoreController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/servers","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/servers\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/servers","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@store"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/servers\/{id}\/details","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@details"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/servers\/{id}\/container","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@container"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/servers\/{id}\/build","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@build"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/servers\/{id}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@startup"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/servers\/{id}\/install","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@install"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/servers\/{id}\/rebuild","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@rebuild"},{"host":null,"methods":["PATCH"],"uri":"api\/admin\/servers\/{id}\/suspend","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@suspend"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/servers\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServerController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/nodes\/{id}\/config","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@viewConfig"},{"host":null,"methods":["POST"],"uri":"api\/admin\/nodes","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@store"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/nodes\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\NodeController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/users","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/users\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"api\/admin\/users","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@store"},{"host":null,"methods":["PUT"],"uri":"api\/admin\/users\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"api\/admin\/users\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/services","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServiceController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/admin\/services\/{id}","name":null,"action":"Pterodactyl\Http\Controllers\API\Admin\ServiceController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":"account.security.totp.set","action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":"account.security.totp.disable","action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{location}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/view\/{location}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{host}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getSettings"},{"host":null,"methods":["POST"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\BaseController@postSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{user}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/nodes","name":"admin.servers.new.nodes","action":"Pterodactyl\Http\Controllers\Admin\ServersController@nodes"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details\/container","name":"admin.servers.view.details.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@setContainer"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{server}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["PATCH"],"uri":"admin\/nodes\/view\/{node}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services","name":"admin.services","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/new","name":"admin.services.new","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/view\/{service}","name":"admin.services.view","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/view\/{service}\/functions","name":"admin.services.view.functions","action":"Pterodactyl\Http\Controllers\Admin\ServiceController@viewFunctions"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/option\/new","name":"admin.services.option.new","action":"Pterodactyl\Http\Controllers\Admin\OptionController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/option\/{option}","name":"admin.services.option.view","action":"Pterodactyl\Http\Controllers\Admin\OptionController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/option\/{option}\/variables","name":"admin.services.option.variables","action":"Pterodactyl\Http\Controllers\Admin\VariableController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/services\/option\/{option}\/scripts","name":"admin.services.option.scripts","action":"Pterodactyl\Http\Controllers\Admin\OptionController@viewScripts"},{"host":null,"methods":["POST"],"uri":"admin\/services\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@store"},{"host":null,"methods":["POST"],"uri":"admin\/services\/option\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\OptionController@store"},{"host":null,"methods":["POST"],"uri":"admin\/services\/option\/{option}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\VariableController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/services\/view\/{service}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/services\/view\/{service}\/functions","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@updateFunctions"},{"host":null,"methods":["PATCH"],"uri":"admin\/services\/option\/{option}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\OptionController@editConfiguration"},{"host":null,"methods":["PATCH"],"uri":"admin\/services\/option\/{option}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\OptionController@updateScripts"},{"host":null,"methods":["PATCH"],"uri":"admin\/services\/option\/{option}\/variables\/{variable}","name":"admin.services.option.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\VariableController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/view\/{service}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServiceController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/option\/{option}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\OptionController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/services\/option\/{option}\/variables\/{variable}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\VariableController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{pack}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{pack}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["PATCH"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@totpCheckpoint"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@console"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/databases","name":"server.settings.databases","action":"Pterodactyl\Http\Controllers\Server\ServerController@getDatabases"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\ServerController@getSFTP"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\ServerController@getStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAllocation"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/sftp","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsSFTP"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\DownloadController@index"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@directory"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{subuser}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/view\/{subuser}\/delete","name":"server.subusers.delete","action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules","name":"server.schedules","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/new","name":"server.schedules.new","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":"server.schedules.view","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@view"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@store"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@update"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/toggle","name":"server.schedules.toggle","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskToggleController@index"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/delete","name":"server.schedules.delete","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@delete"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/ajax\/settings\/reset-database-password","name":"server.ajax.reset-database-password","action":"Pterodactyl\Http\Controllers\Server\AjaxController@postResetDatabasePassword"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/authenticate\/{token}","name":"post.api.remote.authenticate","action":"Pterodactyl\Http\Controllers\API\Remote\ValidateKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services","name":"daemon.services","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@listServices"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/services\/pull\/{service}\/{file}","name":"daemon.pull","action":"Pterodactyl\Http\Controllers\Daemon\ServiceController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/details\/option\/{server}","name":"daemon.option.details","action":"Pterodactyl\Http\Controllers\Daemon\OptionController@details"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"}], + routes : [{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":"account.security.totp.set","action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":"account.security.totp.disable","action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{location}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/view\/{location}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{host}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getSettings"},{"host":null,"methods":["POST"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\BaseController@postSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{user}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/nodes","name":"admin.servers.new.nodes","action":"Pterodactyl\Http\Controllers\Admin\ServersController@nodes"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details\/container","name":"admin.servers.view.details.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@setContainer"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{server}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["PATCH"],"uri":"admin\/nodes\/view\/{node}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests","name":"admin.nests","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/new","name":"admin.nests.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/view\/{nest}","name":"admin.nests.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/new","name":"admin.nests.egg.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}","name":"admin.nests.egg.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/export","name":"admin.nests.egg.export","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":"admin.nests.egg.variables","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":"admin.nests.egg.scripts","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@index"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/import","name":"admin.nests.egg.import","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@import"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@store"},{"host":null,"methods":["PUT"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":"admin.nests.egg.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{pack}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{pack}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["PATCH"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@totpCheckpoint"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@console"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\ServerController@getSFTP"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\ServerController@getStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\ServerController@getAllocation"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/sftp","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsSFTP"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\ServerController@postSettingsStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/databases","name":"server.databases.index","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/databases\/password","name":"server.databases.password","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\DownloadController@index"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@directory"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{subuser}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/view\/{subuser}\/delete","name":"server.subusers.delete","action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules","name":"server.schedules","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/new","name":"server.schedules.new","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":"server.schedules.view","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@view"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@store"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@update"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/toggle","name":"server.schedules.toggle","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskToggleController@index"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/delete","name":"server.schedules.delete","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/authenticate\/{token}","name":"api.remote.authenticate","action":"Pterodactyl\Http\Controllers\API\Remote\ValidateKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs","name":"api.remote.eggs","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs\/{uuid}","name":"api.remote.eggs.download","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@download"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/details\/option\/{server}","name":"daemon.option.details","action":"Pterodactyl\Http\Controllers\Daemon\OptionController@details"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"}], prefix: '', route : function (name, parameters, route) { diff --git a/resources/lang/en/navigation.php b/resources/lang/en/navigation.php index 5693d825d..b8f2fb83a 100644 --- a/resources/lang/en/navigation.php +++ b/resources/lang/en/navigation.php @@ -20,11 +20,12 @@ return [ 'subusers' => 'Subusers', 'schedules' => 'Schedules', 'configuration' => 'Configuration', - 'port_allocations' => 'Port Allocations', + 'port_allocations' => 'Allocation Settings', 'sftp_settings' => 'SFTP Settings', 'startup_parameters' => 'Startup Parameters', 'databases' => 'Databases', 'edit_file' => 'Edit File', - 'admin' => 'Manage', + 'admin_header' => 'ADMINISTRATIVE', + 'admin' => 'Server Configuration', ], ]; diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php index 0aa414fd2..3aa27e5b2 100644 --- a/resources/lang/en/server.php +++ b/resources/lang/en/server.php @@ -189,9 +189,13 @@ return [ 'title' => 'Delete Subuser', 'description' => 'Allows a user to delete other subusers on the server.', ], - 'set_connection' => [ - 'title' => 'Set Default Connection', - 'description' => 'Allows user to set the default connection used for a server as well as view avaliable ports.', + 'view_allocations' => [ + 'title' => 'View Allocations', + 'description' => 'Allows user to view all of the IPs and ports assigned to a server.', + ], + 'edit_allocation' => [ + 'title' => 'Edit Default Connection', + 'description' => 'Allows user to change the default connection allocation to use for a server.', ], 'view_startup' => [ 'title' => 'View Startup Command', @@ -288,8 +292,8 @@ return [ 'command' => 'Startup Command', 'edit_params' => 'Edit Parameters', 'update' => 'Update Startup Parameters', - 'startup_var' => 'Startup Command Variable', 'startup_regex' => 'Input Rules', + 'edited' => 'Startup variables have been successfully edited. They will take effect the next time this server is started.', ], 'sftp' => [ 'header' => 'SFTP Configuration', @@ -297,12 +301,12 @@ return [ 'change_pass' => 'Change SFTP Password', 'details' => 'SFTP Details', 'conn_addr' => 'Connection Address', - 'warning' => 'Ensure that your client is set to use SFTP and not FTP or FTPS for connections, there is a difference between the protocols.', + 'warning' => 'The SFTP password is your account password. Ensure that your client is set to use SFTP and not FTP or FTPS for connections, there is a difference between the protocols.', ], 'database' => [ 'header' => 'Databases', 'header_sub' => 'All databases available for this server.', - 'your_dbs' => 'Your Databases', + 'your_dbs' => 'Configured Databases', 'host' => 'MySQL Host', 'reset_password' => 'Reset Password', 'no_dbs' => 'There are no databases listed for this server.', diff --git a/resources/themes/pterodactyl/admin/databases/view.blade.php b/resources/themes/pterodactyl/admin/databases/view.blade.php index 65296e578..427fafb82 100644 --- a/resources/themes/pterodactyl/admin/databases/view.blade.php +++ b/resources/themes/pterodactyl/admin/databases/view.blade.php @@ -79,9 +79,8 @@
diff --git a/resources/themes/pterodactyl/admin/servers/index.blade.php b/resources/themes/pterodactyl/admin/servers/index.blade.php index 1c066a4a0..5619ea2f8 100644 --- a/resources/themes/pterodactyl/admin/servers/index.blade.php +++ b/resources/themes/pterodactyl/admin/servers/index.blade.php @@ -42,7 +42,6 @@{{ $server->uuidShort }}
{{ $server->allocation->alias }}:{{ $server->allocation->port }}
diff --git a/resources/themes/pterodactyl/admin/servers/view/database.blade.php b/resources/themes/pterodactyl/admin/servers/view/database.blade.php
index c76d1fbba..6c556137d 100644
--- a/resources/themes/pterodactyl/admin/servers/view/database.blade.php
+++ b/resources/themes/pterodactyl/admin/servers/view/database.blade.php
@@ -40,6 +40,9 @@
{{ Crypt::decrypt($database->password) }}
+ ••••••••
+
+
+ {{ Crypt::decrypt($database->password) }}
+
+ {{ $database->host->host }}:{{ $database->host->port }}
{{ $allocation->ip }}
@@ -50,9 +50,9 @@
{{ $allocation->port }}
@lang('auth.not_authorized')
-