1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-11-22 17:12:30 +01:00

Merge branch 'develop' into lancepioch-patch-3

This commit is contained in:
Dane Everitt 2019-12-28 11:16:14 -08:00 committed by GitHub
commit e6008d6392
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
361 changed files with 5554 additions and 19849 deletions

View File

@ -1,109 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Console\Commands\Server;
use Webmozart\Assert\Assert;
use Illuminate\Console\Command;
use GuzzleHttp\Exception\RequestException;
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
class RebuildServerCommand extends Command
{
/**
* @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService
*/
protected $configurationStructureService;
/**
* @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
*/
protected $daemonRepository;
/**
* @var string
*/
protected $description = 'Rebuild a single server, all servers on a node, or all servers on the panel.';
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $repository;
/**
* @var string
*/
protected $signature = 'p:server:rebuild
{server? : The ID of the server to rebuild.}
{--node= : ID of the node to rebuild all servers on. Ignored if server is passed.}';
/**
* RebuildServerCommand constructor.
*
* @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonRepository
* @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(
DaemonServerRepository $daemonRepository,
ServerConfigurationStructureService $configurationStructureService,
ServerRepositoryInterface $repository
) {
parent::__construct();
$this->configurationStructureService = $configurationStructureService;
$this->daemonRepository = $daemonRepository;
$this->repository = $repository;
}
/**
* Handle command execution.
*/
public function handle()
{
$servers = $this->getServersToProcess();
$bar = $this->output->createProgressBar(count($servers));
$servers->each(function ($server) use ($bar) {
$bar->clear();
$json = array_merge($this->configurationStructureService->handle($server), ['rebuild' => true]);
try {
$this->daemonRepository->setServer($server)->update($json);
} catch (RequestException $exception) {
$this->output->error(trans('command/messages.server.rebuild_failed', [
'name' => $server->name,
'id' => $server->id,
'node' => $server->node->name,
'message' => $exception->getMessage(),
]));
}
$bar->advance();
$bar->display();
});
$this->line('');
}
/**
* Return the servers to be rebuilt.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
private function getServersToProcess()
{
Assert::nullOrIntegerish($this->argument('server'), 'Value passed in server argument must be null or an integer, received %s.');
Assert::nullOrIntegerish($this->option('node'), 'Value passed in node option must be null or integer, received %s.');
return $this->repository->getDataForRebuild($this->argument('server'), $this->option('node'));
}
}

View File

@ -1,16 +1,9 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Exceptions\Service\User;
use Exception;
use Pterodactyl\Exceptions\DisplayException;
class TwoFactorAuthenticationTokenInvalid extends Exception
class TwoFactorAuthenticationTokenInvalid extends DisplayException
{
}

View File

@ -0,0 +1,48 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin\Nodes;
use Illuminate\Http\Request;
use Illuminate\Contracts\View\Factory;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
class NodeController extends Controller
{
/**
* @var \Illuminate\Contracts\View\Factory
*/
private $view;
/**
* @var \Pterodactyl\Repositories\Eloquent\NodeRepository
*/
private $repository;
/**
* NodeController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $repository
* @param \Illuminate\Contracts\View\Factory $view
*/
public function __construct(NodeRepository $repository, Factory $view)
{
$this->view = $view;
$this->repository = $repository;
}
/**
* Returns a listing of nodes on the system.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
{
$nodes = $this->repository
->setSearchTerm($request->input('query'))
->getNodeListingData();
return $this->view->make('admin.nodes.index', compact('nodes'));
}
}

View File

@ -0,0 +1,160 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin\Nodes;
use Illuminate\Http\Request;
use Pterodactyl\Models\Node;
use Illuminate\Support\Collection;
use Illuminate\Contracts\View\Factory;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Services\Helpers\SoftwareVersionService;
use Pterodactyl\Repositories\Eloquent\LocationRepository;
use Pterodactyl\Repositories\Eloquent\AllocationRepository;
class NodeViewController extends Controller
{
use JavascriptInjection;
/**
* @var \Pterodactyl\Repositories\Eloquent\NodeRepository
*/
private $repository;
/**
* @var \Illuminate\Contracts\View\Factory
*/
private $view;
/**
* @var \Pterodactyl\Services\Helpers\SoftwareVersionService
*/
private $versionService;
/**
* @var \Pterodactyl\Repositories\Eloquent\LocationRepository
*/
private $locationRepository;
/**
* @var \Pterodactyl\Repositories\Eloquent\AllocationRepository
*/
private $allocationRepository;
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
private $serverRepository;
/**
* NodeViewController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $allocationRepository
* @param \Pterodactyl\Repositories\Eloquent\LocationRepository $locationRepository
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $repository
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $serverRepository
* @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService
* @param \Illuminate\Contracts\View\Factory $view
*/
public function __construct(
AllocationRepository $allocationRepository,
LocationRepository $locationRepository,
NodeRepository $repository,
ServerRepository $serverRepository,
SoftwareVersionService $versionService,
Factory $view
) {
$this->repository = $repository;
$this->view = $view;
$this->versionService = $versionService;
$this->locationRepository = $locationRepository;
$this->allocationRepository = $allocationRepository;
$this->serverRepository = $serverRepository;
}
/**
* Returns index view for a specific node on the system.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request, Node $node)
{
$node = $this->repository->loadLocationAndServerCount($node);
return $this->view->make('admin.nodes.view.index', [
'node' => $node,
'stats' => $this->repository->getUsageStats($node),
'version' => $this->versionService,
]);
}
/**
* Returns the settings page for a specific node.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function settings(Request $request, Node $node)
{
return $this->view->make('admin.nodes.view.settings', [
'node' => $node,
'locations' => $this->locationRepository->all(),
]);
}
/**
* Return the node configuration page for a specific node.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function configuration(Request $request, Node $node)
{
return $this->view->make('admin.nodes.view.configuration', compact('node'));
}
/**
* Return the node allocation management page.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function allocations(Request $request, Node $node)
{
$node = $this->repository->loadNodeAllocations($node);
$this->plainInject(['node' => Collection::wrap($node)->only(['id'])]);
return $this->view->make('admin.nodes.view.allocation', [
'node' => $node,
'allocations' => $this->allocationRepository->setColumns(['ip'])->getUniqueAllocationIpsForNode($node->id),
]);
}
/**
* Return a listing of servers that exist for this specific node.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function servers(Request $request, Node $node)
{
$this->plainInject([
'node' => Collection::wrap($node->makeVisible('daemonSecret'))
->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']),
]);
return $this->view->make('admin.nodes.view.servers', [
'node' => $node,
'servers' => $this->serverRepository->loadAllServersForNode($node->id, 25),
]);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin\Nodes;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Pterodactyl\Models\Node;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
class SystemInformationController extends Controller
{
/**
* @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository
*/
private $repository;
/**
* SystemInformationController constructor.
*
* @param \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository $repository
*/
public function __construct(DaemonConfigurationRepository $repository)
{
$this->repository = $repository;
}
/**
* Returns system information from the Daemon.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function __invoke(Request $request, Node $node)
{
$data = $this->repository->setNode($node)->getSystemInformation();
return JsonResponse::create([
'version' => $data['version'] ?? '',
'system' => [
'type' => Str::title($data['os'] ?? 'Unknown'),
'arch' => $data['architecture'] ?? '--',
'release' => $data['kernel_version'] ?? '--',
'cpus' => $data['cpu_count'] ?? 0,
],
]);
}
}

View File

@ -9,7 +9,6 @@
namespace Pterodactyl\Http\Controllers\Admin;
use Javascript;
use Cake\Chronos\Chronos;
use Illuminate\Http\Request;
use Pterodactyl\Models\Node;
@ -138,19 +137,6 @@ class NodesController extends Controller
$this->versionService = $versionService;
}
/**
* Displays the index page listing all nodes on the panel.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
return view('admin.nodes.index', [
'nodes' => $this->repository->setSearchTerm($request->input('query'))->getNodeListingData(),
]);
}
/**
* Displays create new node page.
*
@ -184,79 +170,6 @@ class NodesController extends Controller
return redirect()->route('admin.nodes.view.allocation', $node->id);
}
/**
* Shows the index overview page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewIndex(Node $node)
{
return view('admin.nodes.view.index', [
'node' => $this->repository->loadLocationAndServerCount($node),
'stats' => $this->repository->getUsageStats($node),
'version' => $this->versionService,
]);
}
/**
* Shows the settings page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewSettings(Node $node)
{
return view('admin.nodes.view.settings', [
'node' => $node,
'locations' => $this->locationRepository->all(),
]);
}
/**
* Shows the configuration page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewConfiguration(Node $node)
{
return view('admin.nodes.view.configuration', ['node' => $node]);
}
/**
* Shows the allocation page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewAllocation(Node $node)
{
$this->repository->loadNodeAllocations($node);
Javascript::put(['node' => collect($node)->only(['id'])]);
return view('admin.nodes.view.allocation', [
'allocations' => $this->allocationRepository->setColumns(['ip'])->getUniqueAllocationIpsForNode($node->id),
'node' => $node,
]);
}
/**
* Shows the server listing page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewServers(Node $node)
{
$servers = $this->serverRepository->loadAllServersForNode($node->id, 25);
Javascript::put([
'node' => collect($node->makeVisible('daemonSecret'))->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']),
]);
return view('admin.nodes.view.servers', ['node' => $node, 'servers' => $servers]);
}
/**
* Updates settings for a node.
*

View File

@ -0,0 +1,132 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin\Servers;
use JavaScript;
use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\NestRepository;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Http\Requests\Admin\ServerFormRequest;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Services\Servers\ServerCreationService;
use Pterodactyl\Repositories\Eloquent\LocationRepository;
class CreateServerController extends Controller
{
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
private $repository;
/**
* @var \Pterodactyl\Repositories\Eloquent\NodeRepository
*/
private $nodeRepository;
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
private $alert;
/**
* @var \Pterodactyl\Repositories\Eloquent\NestRepository
*/
private $nestRepository;
/**
* @var \Pterodactyl\Repositories\Eloquent\LocationRepository
*/
private $locationRepository;
/**
* @var \Pterodactyl\Services\Servers\ServerCreationService
*/
private $creationService;
/**
* CreateServerController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Repositories\Eloquent\NestRepository $nestRepository
* @param \Pterodactyl\Repositories\Eloquent\LocationRepository $locationRepository
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
* @param \Pterodactyl\Services\Servers\ServerCreationService $creationService
*/
public function __construct(
AlertsMessageBag $alert,
NestRepository $nestRepository,
LocationRepository $locationRepository,
NodeRepository $nodeRepository,
ServerRepository $repository,
ServerCreationService $creationService
) {
$this->repository = $repository;
$this->nodeRepository = $nodeRepository;
$this->alert = $alert;
$this->nestRepository = $nestRepository;
$this->locationRepository = $locationRepository;
$this->creationService = $creationService;
}
/**
* Displays the create server page.
*
* @return \Illuminate\Contracts\View\Factory
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function index()
{
$nodes = $this->nodeRepository->all();
if (count($nodes) < 1) {
$this->alert->warning(trans('admin/server.alerts.node_required'))->flash();
return redirect()->route('admin.nodes');
}
$nests = $this->nestRepository->getWithEggs();
Javascript::put([
'nodeData' => $this->nodeRepository->getNodesForServerCreation(),
'nests' => $nests->map(function ($item) {
return array_merge($item->toArray(), [
'eggs' => $item->eggs->keyBy('id')->toArray(),
]);
})->keyBy('id'),
]);
return view('admin.servers.new', [
'locations' => $this->locationRepository->all(),
'nests' => $nests,
]);
}
/**
* Create a new server on the remote system.
*
* @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
* @throws \Throwable
*/
public function store(ServerFormRequest $request)
{
$server = $this->creationService->handle(
$request->except(['_token'])
);
$this->alert->success(
trans('admin/server.alerts.server_created')
)->flash();
return RedirectResponse::create('/admin/servers/view/' . $server->id);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin\Servers;
use Illuminate\Http\Request;
use Illuminate\Contracts\View\Factory;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
class ServerController extends Controller
{
/**
* @var \Illuminate\Contracts\View\Factory
*/
private $view;
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
private $repository;
/**
* ServerController constructor.
*
* @param \Illuminate\Contracts\View\Factory $view
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
*/
public function __construct(
Factory $view,
ServerRepository $repository
) {
$this->view = $view;
$this->repository = $repository;
}
/**
* Returns all of the servers that exist on the system using a paginated result set. If
* a query is passed along in the request it is also passed to the repository function.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
{
return $this->view->make('admin.servers.index', [
'servers' => $this->repository->setSearchTerm($request->input('query'))->getAllServers(
config()->get('pterodactyl.paginate.admin.servers')
),
]);
}
}

View File

@ -0,0 +1,176 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin\Servers;
use Illuminate\Http\Request;
use Pterodactyl\Models\Nest;
use Pterodactyl\Models\Server;
use Illuminate\Contracts\View\Factory;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\NestRepository;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
class ServerViewController extends Controller
{
use JavascriptInjection;
/**
* @var \Illuminate\Contracts\View\Factory
*/
private $view;
/**
* @var \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository
*/
private $databaseHostRepository;
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
private $repository;
/**
* @var \Pterodactyl\Repositories\Eloquent\NestRepository
*/
private $nestRepository;
/**
* ServerViewController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository
* @param \Pterodactyl\Repositories\Eloquent\NestRepository $nestRepository
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
* @param \Illuminate\Contracts\View\Factory $view
*/
public function __construct(
DatabaseHostRepository $databaseHostRepository,
NestRepository $nestRepository,
ServerRepository $repository,
Factory $view
) {
$this->view = $view;
$this->databaseHostRepository = $databaseHostRepository;
$this->repository = $repository;
$this->nestRepository = $nestRepository;
}
/**
* Returns the index view for a server.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request, Server $server)
{
return $this->view->make('admin.servers.view.index', compact('server'));
}
/**
* Returns the server details page.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Contracts\View\View
*/
public function details(Request $request, Server $server)
{
return $this->view->make('admin.servers.view.details', compact('server'));
}
/**
* Returns a view of server build settings.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Contracts\View\View
*/
public function build(Request $request, Server $server)
{
$allocations = $server->node->allocations->toBase();
return $this->view->make('admin.servers.view.build', [
'server' => $server,
'assigned' => $allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'),
'unassigned' => $allocations->where('server_id', null)->sortBy('port')->sortBy('ip'),
]);
}
/**
* Returns the server startup management page.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Contracts\View\View
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function startup(Request $request, Server $server)
{
$parameters = $this->repository->getVariablesWithValues($server->id, true);
$nests = $this->nestRepository->getWithEggs();
$this->plainInject([
'server' => $server,
'server_variables' => $parameters->data,
'nests' => $nests->map(function (Nest $item) {
return array_merge($item->toArray(), [
'eggs' => $item->eggs->keyBy('id')->toArray(),
]);
})->keyBy('id'),
]);
return $this->view->make('admin.servers.view.startup', compact('server', 'nests'));
}
/**
* Returns all of the databases that exist for the server.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Contracts\View\View
*/
public function database(Request $request, Server $server)
{
return $this->view->make('admin.servers.view.database', [
'hosts' => $this->databaseHostRepository->all(),
'server' => $server,
]);
}
/**
* Returns the base server management page, or an exception if the server
* is in a state that cannot be recovered from.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Contracts\View\View
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function manage(Request $request, Server $server)
{
if ($server->installed > 1) {
throw new DisplayException(
'This server is in a failed install state and cannot be recovered. Please delete and re-create the server.'
);
}
return $this->view->make('admin.servers.view.manage', compact('server'));
}
/**
* Returns the server deletion page.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Contracts\View\View
*/
public function delete(Request $request, Server $server)
{
return $this->view->make('admin.servers.view.delete', compact('server'));
}
}

View File

@ -9,7 +9,6 @@
namespace Pterodactyl\Http\Controllers\Admin;
use Javascript;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
@ -17,23 +16,18 @@ use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Servers\SuspensionService;
use Pterodactyl\Http\Requests\Admin\ServerFormRequest;
use Pterodactyl\Services\Servers\ServerCreationService;
use Pterodactyl\Services\Servers\ServerDeletionService;
use Pterodactyl\Services\Servers\ReinstallServerService;
use Pterodactyl\Services\Servers\ContainerRebuildService;
use Pterodactyl\Services\Servers\BuildModificationService;
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;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest;
@ -59,11 +53,6 @@ class ServersController extends Controller
*/
protected $config;
/**
* @var \Pterodactyl\Services\Servers\ContainerRebuildService
*/
protected $containerRebuildService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
*/
@ -94,21 +83,11 @@ class ServersController extends Controller
*/
protected $detailsModificationService;
/**
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface
*/
protected $locationRepository;
/**
* @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface
*/
protected $nestRepository;
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
*/
protected $nodeRepository;
/**
* @var \Pterodactyl\Services\Servers\ReinstallServerService
*/
@ -119,11 +98,6 @@ class ServersController extends Controller
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Servers\ServerCreationService
*/
protected $service;
/**
* @var \Pterodactyl\Services\Servers\StartupModificationService
*/
@ -141,16 +115,12 @@ class ServersController extends Controller
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
* @param \Pterodactyl\Services\Servers\BuildModificationService $buildModificationService
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Pterodactyl\Services\Servers\ContainerRebuildService $containerRebuildService
* @param \Pterodactyl\Services\Servers\ServerCreationService $service
* @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
* @param \Pterodactyl\Services\Servers\DetailsModificationService $detailsModificationService
* @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository
* @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallService
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $nestRepository
@ -162,16 +132,12 @@ class ServersController extends Controller
AllocationRepositoryInterface $allocationRepository,
BuildModificationService $buildModificationService,
ConfigRepository $config,
ContainerRebuildService $containerRebuildService,
ServerCreationService $service,
DatabaseManagementService $databaseManagementService,
DatabasePasswordService $databasePasswordService,
DatabaseRepositoryInterface $databaseRepository,
DatabaseHostRepository $databaseHostRepository,
ServerDeletionService $deletionService,
DetailsModificationService $detailsModificationService,
LocationRepositoryInterface $locationRepository,
NodeRepositoryInterface $nodeRepository,
ReinstallServerService $reinstallService,
ServerRepositoryInterface $repository,
NestRepositoryInterface $nestRepository,
@ -182,223 +148,19 @@ class ServersController extends Controller
$this->allocationRepository = $allocationRepository;
$this->buildModificationService = $buildModificationService;
$this->config = $config;
$this->containerRebuildService = $containerRebuildService;
$this->databaseHostRepository = $databaseHostRepository;
$this->databaseManagementService = $databaseManagementService;
$this->databasePasswordService = $databasePasswordService;
$this->databaseRepository = $databaseRepository;
$this->detailsModificationService = $detailsModificationService;
$this->deletionService = $deletionService;
$this->locationRepository = $locationRepository;
$this->nestRepository = $nestRepository;
$this->nodeRepository = $nodeRepository;
$this->reinstallService = $reinstallService;
$this->repository = $repository;
$this->service = $service;
$this->startupModificationService = $startupModificationService;
$this->suspensionService = $suspensionService;
}
/**
* Display the index page with all servers currently on the system.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
return view('admin.servers.index', [
'servers' => $this->repository->setSearchTerm($request->input('query'))->getAllServers(
$this->config->get('pterodactyl.paginate.admin.servers')
),
]);
}
/**
* Display create new server page.
*
* @return \Illuminate\View\View
*
* @throws \Exception
*/
public function create()
{
$nodes = $this->nodeRepository->all();
if (count($nodes) < 1) {
$this->alert->warning(trans('admin/server.alerts.node_required'))->flash();
return redirect()->route('admin.nodes');
}
$nests = $this->nestRepository->getWithEggs();
Javascript::put([
'nodeData' => $this->nodeRepository->getNodesForServerCreation(),
'nests' => $nests->map(function ($item) {
return array_merge($item->toArray(), [
'eggs' => $item->eggs->keyBy('id')->toArray(),
]);
})->keyBy('id'),
]);
return view('admin.servers.new', [
'locations' => $this->locationRepository->all(),
'nests' => $nests,
]);
}
/**
* Handle POST of server creation form.
*
* @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
*/
public function store(ServerFormRequest $request)
{
$server = $this->service->handle($request->except('_token'));
$this->alert->success(trans('admin/server.alerts.server_created'))->flash();
return redirect()->route('admin.servers.view', $server->id);
}
/**
* Display the index when viewing a specific server.
*
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\View\View
*/
public function viewIndex(Server $server)
{
return view('admin.servers.view.index', ['server' => $server]);
}
/**
* Display the details page when viewing a specific server.
*
* @param int $server
* @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function viewDetails($server)
{
return view('admin.servers.view.details', [
'server' => $this->repository->findFirstWhere([
['id', '=', $server],
['installed', '=', 1],
]),
]);
}
/**
* Display the build details page when viewing a specific server.
*
* @param int $server
* @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function viewBuild($server)
{
$server = $this->repository->findFirstWhere([
['id', '=', $server],
['installed', '=', 1],
]);
$allocations = $this->allocationRepository->getAllocationsForNode($server->node_id);
return view('admin.servers.view.build', [
'server' => $server,
'assigned' => $allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'),
'unassigned' => $allocations->where('server_id', null)->sortBy('port')->sortBy('ip'),
]);
}
/**
* Display startup configuration page for a server.
*
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function viewStartup(Server $server)
{
$parameters = $this->repository->getVariablesWithValues($server->id, true);
if (! $parameters->server->installed) {
abort(404);
}
$nests = $this->nestRepository->getWithEggs();
Javascript::put([
'server' => $server,
'nests' => $nests->map(function ($item) {
return array_merge($item->toArray(), [
'eggs' => $item->eggs->keyBy('id')->toArray(),
]);
})->keyBy('id'),
'server_variables' => $parameters->data,
]);
return view('admin.servers.view.startup', [
'server' => $parameters->server,
'nests' => $nests,
]);
}
/**
* Display the database management page for a specific server.
*
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\View\View
*/
public function viewDatabase(Server $server)
{
$this->repository->loadDatabaseRelations($server);
return view('admin.servers.view.database', [
'hosts' => $this->databaseHostRepository->all(),
'server' => $server,
]);
}
/**
* Display the management page when viewing a specific server.
*
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function viewManage(Server $server)
{
if ($server->installed > 1) {
throw new DisplayException('This server is in a failed installation state and must be deleted and recreated.');
}
return view('admin.servers.view.manage', ['server' => $server]);
}
/**
* Display the deletion page for a server.
*
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\View\View
*/
public function viewDelete(Server $server)
{
return view('admin.servers.view.delete', ['server' => $server]);
}
/**
* Update the details for a server.
*
@ -464,21 +226,6 @@ class ServersController extends Controller
return redirect()->route('admin.servers.view.manage', $server->id);
}
/**
* Setup a server to have a container rebuild.
*
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\RedirectResponse
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function rebuildContainer(Server $server)
{
$this->containerRebuildService->handle($server);
$this->alert->success(trans('admin/server.alerts.rebuild_on_boot'))->flash();
return redirect()->route('admin.servers.view.manage', $server->id);
}
/**
* Manage the suspension status for a server.
*
@ -531,7 +278,7 @@ class ServersController extends Controller
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function delete(Request $request, Server $server)
{
@ -549,7 +296,6 @@ class ServersController extends Controller
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/

View File

@ -147,7 +147,7 @@ class UserController extends Controller
public function store(UserFormRequest $request)
{
$user = $this->creationService->handle($request->normalize());
$this->alert->success($this->translator->trans('admin/user.notices.account_created'))->flash();
$this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash();
return redirect()->route('admin.users.view', $user->id);
}
@ -164,27 +164,11 @@ class UserController extends Controller
*/
public function update(UserFormRequest $request, User $user)
{
$this->updateService->setUserLevel(User::USER_LEVEL_ADMIN);
$data = $this->updateService->handle($user, $request->normalize());
$this->updateService
->setUserLevel(User::USER_LEVEL_ADMIN)
->handle($user, $request->normalize());
if (! empty($data->get('exceptions'))) {
foreach ($data->get('exceptions') as $node => $exception) {
/** @var \GuzzleHttp\Exception\RequestException $exception */
/** @var \GuzzleHttp\Psr7\Response|null $response */
$response = method_exists($exception, 'getResponse') ? $exception->getResponse() : null;
$message = trans('admin/server.exceptions.daemon_exception', [
'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(),
]);
$this->alert->danger(trans('exceptions.users.node_revocation_failed', [
'node' => $node,
'error' => $message,
'link' => route('admin.nodes.view', $node),
]))->flash();
}
}
$this->alert->success($this->translator->trans('admin/user.notices.account_updated'))->flash();
$this->alert->success(trans('admin/user.notices.account_updated'))->flash();
return redirect()->route('admin.users.view', $user->id);
}
@ -193,7 +177,7 @@ class UserController extends Controller
* Get a JSON response of users on the system.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Database\Eloquent\Collection
* @return \Illuminate\Support\Collection
*/
public function json(Request $request)
{

View File

@ -72,9 +72,9 @@ class ServerController extends ApplicationApiController
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Throwable
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException

View File

@ -63,15 +63,16 @@ class ServerDetailsController extends ApplicationApiController
* Update the build details for a specific server.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerBuildConfigurationRequest $request
* @param \Pterodactyl\Models\Server $server
* @return array
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function build(UpdateServerBuildConfigurationRequest $request): array
public function build(UpdateServerBuildConfigurationRequest $request, Server $server): array
{
$server = $this->buildModificationService->handle($request->getModel(Server::class), $request->validated());
$server = $this->buildModificationService->handle($server, $request->validated());
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))

View File

@ -6,17 +6,11 @@ use Illuminate\Http\Response;
use Pterodactyl\Models\Server;
use Pterodactyl\Services\Servers\SuspensionService;
use Pterodactyl\Services\Servers\ReinstallServerService;
use Pterodactyl\Services\Servers\ContainerRebuildService;
use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class ServerManagementController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Services\Servers\ContainerRebuildService
*/
private $rebuildService;
/**
* @var \Pterodactyl\Services\Servers\ReinstallServerService
*/
@ -30,18 +24,15 @@ class ServerManagementController extends ApplicationApiController
/**
* SuspensionController constructor.
*
* @param \Pterodactyl\Services\Servers\ContainerRebuildService $rebuildService
* @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallServerService
* @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService
*/
public function __construct(
ContainerRebuildService $rebuildService,
ReinstallServerService $reinstallServerService,
SuspensionService $suspensionService
) {
parent::__construct();
$this->rebuildService = $rebuildService;
$this->reinstallServerService = $reinstallServerService;
$this->suspensionService = $suspensionService;
}
@ -50,15 +41,14 @@ class ServerManagementController extends ApplicationApiController
* Suspend a server on the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function suspend(ServerWriteRequest $request): Response
public function suspend(ServerWriteRequest $request, Server $server): Response
{
$this->suspensionService->toggle($request->getModel(Server::class), SuspensionService::ACTION_SUSPEND);
$this->suspensionService->toggle($server, SuspensionService::ACTION_SUSPEND);
return $this->returnNoContent();
}
@ -67,15 +57,14 @@ class ServerManagementController extends ApplicationApiController
* Unsuspend a server on the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function unsuspend(ServerWriteRequest $request): Response
public function unsuspend(ServerWriteRequest $request, Server $server): Response
{
$this->suspensionService->toggle($request->getModel(Server::class), SuspensionService::ACTION_UNSUSPEND);
$this->suspensionService->toggle($server, SuspensionService::ACTION_UNSUSPEND);
return $this->returnNoContent();
}
@ -84,30 +73,16 @@ class ServerManagementController extends ApplicationApiController
* Mark a server as needing to be reinstalled.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function reinstall(ServerWriteRequest $request): Response
public function reinstall(ServerWriteRequest $request, Server $server): Response
{
$this->reinstallServerService->reinstall($request->getModel(Server::class));
return $this->returnNoContent();
}
/**
* Mark a server as needing its container rebuilt the next time it is started.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function rebuild(ServerWriteRequest $request): Response
{
$this->rebuildService->handle($request->getModel(Server::class));
$this->reinstallServerService->reinstall($server);
return $this->returnNoContent();
}

View File

@ -3,6 +3,8 @@
namespace Pterodactyl\Http\Controllers\Api\Client;
use Pterodactyl\Models\User;
use Illuminate\Support\Collection;
use Pterodactyl\Models\Permission;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Transformers\Api\Client\ServerTransformer;
use Pterodactyl\Http\Requests\Api\Client\GetServersRequest;
@ -62,4 +64,25 @@ class ClientController extends ClientApiController
->transformWith($this->getTransformer(ServerTransformer::class))
->toArray();
}
/**
* Returns all of the subuser permissions available on the system.
*
* @return array
*/
public function permissions()
{
$permissions = Permission::permissions()->map(function ($values, $key) {
return Collection::make($values)->map(function ($permission) use ($key) {
return $key . '.' . $permission;
})->values()->toArray();
})->flatten();
return [
'object' => 'system_permissions',
'attributes' => [
'permissions' => $permissions,
],
];
}
}

View File

@ -2,15 +2,12 @@
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Carbon\Carbon;
use Ramsey\Uuid\Uuid;
use Illuminate\Http\Response;
use Pterodactyl\Models\Server;
use Illuminate\Http\JsonResponse;
use GuzzleHttp\Exception\TransferException;
use Illuminate\Contracts\Routing\ResponseFactory;
use Pterodactyl\Repositories\Wings\DaemonFileRepository;
use Pterodactyl\Transformers\Daemon\FileObjectTransformer;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest;
@ -18,34 +15,35 @@ use Pterodactyl\Http\Requests\Api\Client\Servers\Files\ListFilesRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DownloadFileRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest;
class FileController extends ClientApiController
{
/**
* @var \Illuminate\Contracts\Cache\Factory
*/
private $cache;
/**
* @var \Pterodactyl\Repositories\Wings\DaemonFileRepository
*/
private $fileRepository;
/**
* @var \Illuminate\Contracts\Routing\ResponseFactory
*/
private $responseFactory;
/**
* FileController constructor.
*
* @param \Illuminate\Contracts\Routing\ResponseFactory $responseFactory
* @param \Pterodactyl\Repositories\Wings\DaemonFileRepository $fileRepository
* @param \Illuminate\Contracts\Cache\Repository $cache
*/
public function __construct(DaemonFileRepository $fileRepository, CacheRepository $cache)
{
public function __construct(
ResponseFactory $responseFactory,
DaemonFileRepository $fileRepository
) {
parent::__construct();
$this->cache = $cache;
$this->fileRepository = $fileRepository;
$this->responseFactory = $responseFactory;
}
/**
@ -85,7 +83,42 @@ class FileController extends ClientApiController
return Response::create(
$this->fileRepository->setServer($server)->getContent(
$request->get('file'), config('pterodactyl.files.max_edit_size')
)
),
Response::HTTP_OK,
['Content-Type' => 'text/plain']
);
}
/**
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest $request
* @param \Pterodactyl\Models\Server $server
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*
* @throws \Exception
*/
public function download(GetFileContentsRequest $request, Server $server)
{
set_time_limit(0);
$request = $this->fileRepository->setServer($server)->streamContent(
$request->get('file')
);
$body = $request->getBody();
preg_match('/filename=(?<name>.*)$/', $request->getHeaderLine('Content-Disposition'), $matches);
return $this->responseFactory->streamDownload(
function () use ($body) {
while (! $body->eof()) {
echo $body->read(128);
}
},
$matches['name'] ?? 'download',
[
'Content-Type' => $request->getHeaderLine('Content-Type'),
'Content-Length' => $request->getHeaderLine('Content-Length'),
]
);
}
@ -169,27 +202,4 @@ class FileController extends ClientApiController
return Response::create('', Response::HTTP_NO_CONTENT);
}
/**
* Configure a reference to a file to download in the cache so that when the
* user hits the Daemon and it verifies with the Panel they'll actually be able
* to download that file.
*
* Returns the token that needs to be used when downloading the file.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\DownloadFileRequest $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\JsonResponse
* @throws \Exception
*/
public function download(DownloadFileRequest $request, Server $server): JsonResponse
{
$token = Uuid::uuid4()->toString();
$this->cache->put(
'Server:Downloads:' . $token, ['server' => $server->uuid, 'path' => $request->route()->parameter('file')], Carbon::now()->addMinutes(5)
);
return JsonResponse::create(['token' => $token]);
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Illuminate\Http\Response;
use Pterodactyl\Models\Server;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest;
class SettingsController extends ClientApiController
{
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
private $repository;
/**
* SettingsController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
*/
public function __construct(ServerRepository $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Renames a server.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function rename(RenameServerRequest $request, Server $server)
{
$this->repository->update($server->id, [
'name' => $request->input('name'),
]);
return Response::create('', Response::HTTP_NO_CONTENT);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Pterodactyl\Models\Server;
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
use Pterodactyl\Transformers\Api\Client\SubuserTransformer;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest;
class SubuserController extends ClientApiController
{
/**
* @var \Pterodactyl\Repositories\Eloquent\SubuserRepository
*/
private $repository;
/**
* SubuserController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository
*/
public function __construct(SubuserRepository $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Return the users associated with this server instance.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest $request
* @param \Pterodactyl\Models\Server $server
* @return array
*/
public function index(GetSubuserRequest $request, Server $server)
{
$users = $this->repository->getSubusersForServer($server->id);
return $this->fractal->collection($users)
->transformWith($this->getTransformer(SubuserTransformer::class))
->toArray();
}
}

View File

@ -45,7 +45,7 @@ class WebsocketController extends ClientApiController
*/
public function __invoke(Request $request, Server $server)
{
if (! $request->user()->can('connect-to-ws', $server)) {
if (! $request->user()->can('websocket.*', $server)) {
throw new HttpException(
Response::HTTP_FORBIDDEN, 'You do not have permission to connect to this server\'s websocket.'
);
@ -63,7 +63,11 @@ class WebsocketController extends ClientApiController
->expiresAt($now->addMinutes(15)->getTimestamp())
->withClaim('user_id', $request->user()->id)
->withClaim('server_uuid', $server->uuid)
->withClaim('permissions', ['connect', 'send-command', 'send-power'])
->withClaim('permissions', array_merge([
'connect',
'send-command',
'send-power',
], $request->user()->root_admin ? ['receive-errors'] : []))
->getToken($signer, new Key($server->node->daemonSecret));
$socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $server->node->getConnectionAddress());

View File

@ -0,0 +1,129 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Client;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\Validation\Factory;
use Illuminate\Validation\ValidationException;
use Pterodactyl\Services\Users\TwoFactorSetupService;
use Pterodactyl\Services\Users\ToggleTwoFactorService;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class TwoFactorController extends ClientApiController
{
/**
* @var \Pterodactyl\Services\Users\TwoFactorSetupService
*/
private $setupService;
/**
* @var \Illuminate\Contracts\Validation\Factory
*/
private $validation;
/**
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
*/
private $toggleTwoFactorService;
/**
* TwoFactorController constructor.
*
* @param \Pterodactyl\Services\Users\ToggleTwoFactorService $toggleTwoFactorService
* @param \Pterodactyl\Services\Users\TwoFactorSetupService $setupService
* @param \Illuminate\Contracts\Validation\Factory $validation
*/
public function __construct(
ToggleTwoFactorService $toggleTwoFactorService,
TwoFactorSetupService $setupService,
Factory $validation
) {
parent::__construct();
$this->setupService = $setupService;
$this->validation = $validation;
$this->toggleTwoFactorService = $toggleTwoFactorService;
}
/**
* Returns two-factor token credentials that allow a user to configure
* it on their account. If two-factor is already enabled this endpoint
* will return a 400 error.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function index(Request $request)
{
if ($request->user()->totp_enabled) {
throw new BadRequestHttpException('Two-factor authentication is already enabled on this account.');
}
return JsonResponse::create([
'data' => [
'image_url_data' => $this->setupService->handle($request->user()),
],
]);
}
/**
* Updates a user's account to have two-factor enabled.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Validation\ValidationException
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid
*/
public function store(Request $request)
{
$validator = $this->validation->make($request->all(), [
'code' => 'required|string',
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
$this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true);
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
}
/**
* Disables two-factor authentication on an account if the password provided
* is valid.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function delete(Request $request)
{
if (! password_verify($request->input('password') ?? '', $request->user()->password)) {
throw new BadRequestHttpException(
'The password provided was not valid.'
);
}
/** @var \Pterodactyl\Models\User $user */
$user = $request->user();
$user->update([
'totp_authenticated_at' => Carbon::now(),
'use_totp' => false,
]);
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
}
}

View File

@ -7,8 +7,9 @@ use Illuminate\Http\JsonResponse;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Eggs\EggConfigurationService;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
class ServerConfigurationController extends Controller
class ServerDetailsController extends Controller
{
/**
* @var \Pterodactyl\Services\Eggs\EggConfigurationService
@ -20,21 +21,34 @@ class ServerConfigurationController extends Controller
*/
private $repository;
/**
* @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService
*/
private $configurationStructureService;
/**
* ServerConfigurationController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
* @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService
* @param \Pterodactyl\Services\Eggs\EggConfigurationService $eggConfigurationService
*/
public function __construct(ServerRepository $repository, EggConfigurationService $eggConfigurationService)
{
public function __construct(
ServerRepository $repository,
ServerConfigurationStructureService $configurationStructureService,
EggConfigurationService $eggConfigurationService
) {
$this->eggConfigurationService = $eggConfigurationService;
$this->repository = $repository;
$this->configurationStructureService = $configurationStructureService;
}
/**
* Returns details about the server that allows Wings to self-recover and ensure
* that the state of the server matches the Panel at all times.
*
* @param \Illuminate\Http\Request $request
* @param $uuid
* @param string $uuid
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
@ -43,8 +57,9 @@ class ServerConfigurationController extends Controller
{
$server = $this->repository->getByUuid($uuid);
return JsonResponse::create(
$this->eggConfigurationService->handle($server)
);
return JsonResponse::create([
'settings' => $this->configurationStructureService->handle($server),
'process_configuration' => $this->eggConfigurationService->handle($server),
]);
}
}

View File

@ -12,7 +12,7 @@ use Pterodactyl\Services\Sftp\AuthenticateUsingPasswordService;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest;
class SftpController extends Controller
class SftpAuthenticationController extends Controller
{
use ThrottlesLogins;
@ -40,9 +40,12 @@ class SftpController extends Controller
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function index(SftpAuthenticationFormRequest $request): JsonResponse
public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse
{
// Reverse the string to avoid issues with usernames that contain periods.
$parts = explode('.', strrev($request->input('username')), 2);
// Unreverse the strings after parsing them apart.
$connection = [
'username' => strrev(array_get($parts, 1)),
'server' => strrev(array_get($parts, 0)),
@ -86,6 +89,8 @@ class SftpController extends Controller
*/
protected function throttleKey(Request $request)
{
return strtolower(array_get(explode('.', $request->input('username')), 0) . '|' . $request->ip());
$username = explode('.', strrev($request->input('username', '')));
return strtolower(strrev($username[0] ?? '') . '|' . $request->ip());
}
}

View File

@ -1,107 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pterodactyl\Models\ApiKey;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Api\KeyCreationService;
use Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
class AccountKeyController extends Controller
{
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
protected $alert;
/**
* @var \Pterodactyl\Services\Api\KeyCreationService
*/
protected $keyService;
/**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
*/
protected $repository;
/**
* APIController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
* @param \Pterodactyl\Services\Api\KeyCreationService $keyService
*/
public function __construct(
AlertsMessageBag $alert,
ApiKeyRepositoryInterface $repository,
KeyCreationService $keyService
) {
$this->alert = $alert;
$this->keyService = $keyService;
$this->repository = $repository;
}
/**
* Display a listing of all account API keys.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request): View
{
return view('base.api.index', [
'keys' => $this->repository->getAccountKeys($request->user()),
]);
}
/**
* Display account API key creation page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function create(Request $request): View
{
return view('base.api.new');
}
/**
* Handle saving new account API key.
*
* @param \Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(StoreAccountKeyRequest $request)
{
$this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([
'user_id' => $request->user()->id,
'allowed_ips' => $request->input('allowed_ips'),
'memo' => $request->input('memo'),
]);
$this->alert->success(trans('base.api.index.keypair_created'))->flash();
return redirect()->route('account.api');
}
/**
* Delete an account API key from the Panel via an AJAX request.
*
* @param \Illuminate\Http\Request $request
* @param string $identifier
* @return \Illuminate\Http\Response
*/
public function revoke(Request $request, string $identifier): Response
{
$this->repository->deleteAccountKey($request->user(), $identifier);
return response('', 204);
}
}

View File

@ -1,109 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pterodactyl\Models\ApiKey;
use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Api\KeyCreationService;
use Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
class ClientApiController extends Controller
{
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
private $alert;
/**
* @var \Pterodactyl\Services\Api\KeyCreationService
*/
private $creationService;
/**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
*/
private $repository;
/**
* ClientApiController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
* @param \Pterodactyl\Services\Api\KeyCreationService $creationService
*/
public function __construct(AlertsMessageBag $alert, ApiKeyRepositoryInterface $repository, KeyCreationService $creationService)
{
$this->alert = $alert;
$this->creationService = $creationService;
$this->repository = $repository;
}
/**
* Return all of the API keys available to this user.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request): View
{
return view('base.api.index', [
'keys' => $this->repository->getAccountKeys($request->user()),
]);
}
/**
* Render UI to allow creation of an API key.
*
* @return \Illuminate\View\View
*/
public function create(): View
{
return view('base.api.new');
}
/**
* Create the API key and return the user to the key listing page.
*
* @param \Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(CreateClientApiKeyRequest $request): RedirectResponse
{
$allowedIps = null;
if (! is_null($request->input('allowed_ips'))) {
$allowedIps = json_encode(explode(PHP_EOL, $request->input('allowed_ips')));
}
$this->creationService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([
'memo' => $request->input('memo'),
'allowed_ips' => $allowedIps,
'user_id' => $request->user()->id,
]);
$this->alert->success('A new client API key has been generated for your account.')->flash();
return redirect()->route('account.api');
}
/**
* Delete a client's API key from the panel.
*
* @param \Illuminate\Http\Request $request
* @param $identifier
* @return \Illuminate\Http\Response
*/
public function delete(Request $request, $identifier): Response
{
$this->repository->deleteAccountKey($request->user(), $identifier);
return response('', 204);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Translation\Translator;
use Pterodactyl\Http\Controllers\Controller;
class LocaleController extends Controller
{
/**
* @var \Illuminate\Translation\Translator
*/
private $translator;
/**
* LocaleController constructor.
*
* @param \Illuminate\Translation\Translator $translator
*/
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
/**
* Returns translation data given a specific locale and namespace.
*
* @param \Illuminate\Http\Request $request
* @param string $locale
* @param string $namespace
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(Request $request, string $locale, string $namespace)
{
$data = $this->translator->getLoader()->load($locale, str_replace('.', '/', $namespace));
return JsonResponse::create($data, 200, [
'E-Tag' => md5(json_encode($data)),
]);
}
}

View File

@ -1,134 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Users\TwoFactorSetupService;
use Pterodactyl\Services\Users\ToggleTwoFactorService;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Pterodactyl\Contracts\Repository\SessionRepositoryInterface;
use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
class SecurityController extends Controller
{
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
protected $alert;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
protected $config;
/**
* @var \Pterodactyl\Contracts\Repository\SessionRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
*/
protected $toggleTwoFactorService;
/**
* @var \Pterodactyl\Services\Users\TwoFactorSetupService
*/
protected $twoFactorSetupService;
/**
* SecurityController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Pterodactyl\Contracts\Repository\SessionRepositoryInterface $repository
* @param \Pterodactyl\Services\Users\ToggleTwoFactorService $toggleTwoFactorService
* @param \Pterodactyl\Services\Users\TwoFactorSetupService $twoFactorSetupService
*/
public function __construct(
AlertsMessageBag $alert,
ConfigRepository $config,
SessionRepositoryInterface $repository,
ToggleTwoFactorService $toggleTwoFactorService,
TwoFactorSetupService $twoFactorSetupService
) {
$this->alert = $alert;
$this->config = $config;
$this->repository = $repository;
$this->toggleTwoFactorService = $toggleTwoFactorService;
$this->twoFactorSetupService = $twoFactorSetupService;
}
/**
* Return information about the user's two-factor authentication status. If not enabled setup their
* secret and return information to allow the user to proceede with setup.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function index(Request $request): JsonResponse
{
if ($request->user()->use_totp) {
return JsonResponse::create([
'enabled' => true,
]);
}
$response = $this->twoFactorSetupService->handle($request->user());
return JsonResponse::create([
'enabled' => false,
'qr_image' => $response,
'secret' => '',
]);
}
/**
* Verifies that 2FA token received is valid and will work on the account.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function store(Request $request): JsonResponse
{
try {
$this->toggleTwoFactorService->handle($request->user(), $request->input('token') ?? '');
} catch (TwoFactorAuthenticationTokenInvalid $exception) {
$error = true;
}
return JsonResponse::create([
'success' => ! isset($error),
]);
}
/**
* Disables TOTP on an account.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function delete(Request $request): JsonResponse
{
try {
$this->toggleTwoFactorService->handle($request->user(), $request->input('token') ?? '', false);
} catch (TwoFactorAuthenticationTokenInvalid $exception) {
$error = true;
}
return JsonResponse::create([
'success' => ! isset($error),
]);
}
}

View File

@ -102,6 +102,6 @@ class ActionController extends Controller
$node = Node::findOrFail($nodeId);
// Manually as getConfigurationAsJson() returns it in correct format already
return response($node->getConfigurationAsJson())->header('Content-Type', 'text/json');
return [];
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Pterodactyl\Http\Middleware\Admin\Servers;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pterodactyl\Models\Server;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ServerInstalled
{
/**
* Checks that the server is installed before allowing access through the route.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
/** @var \Pterodactyl\Models\Server|null $server */
$server = $request->route()->parameter('server');
if (! $server instanceof Server) {
throw new NotFoundHttpException(
'No server resource was located in the request parameters.'
);
}
if ($server->installed !== 1) {
throw new HttpException(
Response::HTTP_FORBIDDEN, 'Access to this resource is not allowed due to the current installation state.'
);
}
return $next($request);
}
}

View File

@ -6,8 +6,11 @@ use Closure;
use stdClass;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pterodactyl\Events\Auth\FailedCaptcha;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Events\Dispatcher;
use Symfony\Component\HttpKernel\Exception\HttpException;
class VerifyReCaptcha
{
@ -16,14 +19,21 @@ class VerifyReCaptcha
*/
private $config;
/**
* @var \Illuminate\Contracts\Events\Dispatcher
*/
private $dispatcher;
/**
* VerifyReCaptcha constructor.
*
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(Repository $config)
public function __construct(Dispatcher $dispatcher, Repository $config)
{
$this->config = $config;
$this->dispatcher = $dispatcher;
}
/**
@ -57,10 +67,15 @@ class VerifyReCaptcha
}
}
// Emit an event and return to the previous view with an error (only the captcha error will be shown!)
event(new FailedCaptcha($request->ip(), (! isset($result) ?: object_get($result, 'hostname'))));
$this->dispatcher->dispatch(
new FailedCaptcha(
$request->ip(), ! empty($result) ? ($result->hostname ?? null) : null
)
);
return redirect()->back()->withErrors(['g-recaptcha-response' => trans('strings.captcha_invalid')])->withInput();
throw new HttpException(
Response::HTTP_BAD_REQUEST, 'Failed to validate reCAPTCHA data.'
);
}
/**

View File

@ -3,6 +3,7 @@
namespace Pterodactyl\Http\Requests\Admin;
use Pterodactyl\Models\User;
use Illuminate\Support\Collection;
class UserFormRequest extends AdminFormRequest
{
@ -12,16 +13,16 @@ class UserFormRequest extends AdminFormRequest
*/
public function rules()
{
$rules = collect(User::getRules());
if ($this->method() === 'PATCH') {
$rules = collect(User::getRulesForUpdate($this->route()->parameter('user')))->merge([
'ignore_connection_error' => ['sometimes', 'nullable', 'boolean'],
]);
}
return $rules->only([
'email', 'username', 'name_first', 'name_last', 'password',
'language', 'ignore_connection_error', 'root_admin',
return Collection::make(
User::getRulesForUpdate($this->route()->parameter('user'))
)->only([
'email',
'username',
'name_first',
'name_last',
'password',
'language',
'root_admin',
])->toArray();
}
}

View File

@ -14,7 +14,7 @@ class DeleteDatabaseRequest extends ClientApiRequest implements ClientPermission
*/
public function permission(): string
{
return 'delete-database';
return 'database.delete';
}
/**

View File

@ -12,6 +12,6 @@ class GetDatabasesRequest extends ClientApiRequest implements ClientPermissionsR
*/
public function permission(): string
{
return 'view-databases';
return 'database.read';
}
}

View File

@ -14,6 +14,6 @@ class RotatePasswordRequest extends ClientApiRequest
*/
public function authorize(): bool
{
return $this->user()->can('reset-db-password', $this->getModel(Server::class));
return $this->user()->can('database.update', $this->getModel(Server::class));
}
}

View File

@ -12,7 +12,7 @@ class StoreDatabaseRequest extends ClientApiRequest implements ClientPermissions
*/
public function permission(): string
{
return 'create-database';
return 'database.create';
}
/**

View File

@ -12,7 +12,7 @@ class CopyFileRequest extends ClientApiRequest implements ClientPermissionsReque
*/
public function permission(): string
{
return 'copy-files';
return 'file.create';
}
/**

View File

@ -14,7 +14,7 @@ class CreateFolderRequest extends ClientApiRequest
*/
public function authorize(): bool
{
return $this->user()->can('create-files', $this->getModel(Server::class));
return $this->user()->can('file.create', $this->getModel(Server::class));
}
/**

View File

@ -12,7 +12,7 @@ class DeleteFileRequest extends ClientApiRequest implements ClientPermissionsReq
*/
public function permission(): string
{
return 'delete-files';
return 'file.delete';
}
/**

View File

@ -15,6 +15,6 @@ class DownloadFileRequest extends ClientApiRequest
*/
public function authorize(): bool
{
return $this->user()->can('download-files', $this->getModel(Server::class));
return $this->user()->can('file.read', $this->getModel(Server::class));
}
}

View File

@ -16,7 +16,7 @@ class GetFileContentsRequest extends ClientApiRequest implements ClientPermissio
*/
public function permission(): string
{
return 'edit-files';
return 'file.read';
}
/**

View File

@ -15,7 +15,7 @@ class ListFilesRequest extends ClientApiRequest
*/
public function authorize(): bool
{
return $this->user()->can('list-files', $this->getModel(Server::class));
return $this->user()->can('file.read', $this->getModel(Server::class));
}
/**

View File

@ -15,7 +15,7 @@ class RenameFileRequest extends ClientApiRequest implements ClientPermissionsReq
*/
public function permission(): string
{
return 'move-files';
return 'file.update';
}
/**

View File

@ -16,7 +16,7 @@ class WriteFileContentRequest extends ClientApiRequest implements ClientPermissi
*/
public function permission(): string
{
return 'save-files';
return 'file.create';
}
/**

View File

@ -15,6 +15,6 @@ class GetNetworkRequest extends ClientApiRequest
*/
public function authorize(): bool
{
return $this->user()->can('view-allocations', $this->getModel(Server::class));
return $this->user()->can('allocation.read', $this->getModel(Server::class));
}
}

View File

@ -13,7 +13,7 @@ class SendCommandRequest extends GetServerRequest
*/
public function authorize(): bool
{
return $this->user()->can('send-command', $this->getModel(Server::class));
return $this->user()->can('control.console', $this->getModel(Server::class));
}
/**

View File

@ -14,7 +14,7 @@ class SendPowerRequest extends ClientApiRequest
*/
public function authorize(): bool
{
return $this->user()->can('power-' . $this->input('signal', '_undefined'), $this->getModel(Server::class));
return $this->user()->can('control.' . $this->input('signal', ''), $this->getModel(Server::class));
}
/**

View File

@ -0,0 +1,34 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Settings;
use Pterodactyl\Models\Server;
use Pterodactyl\Contracts\Http\ClientPermissionsRequest;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class RenameServerRequest extends ClientApiRequest implements ClientPermissionsRequest
{
/**
* Returns the permissions string indicating which permission should be used to
* validate that the authenticated user has permission to perform this action aganist
* the given resource (server).
*
* @return string
*/
public function permission(): string
{
return 'settings.rename';
}
/**
* The rules to apply when validating this request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => Server::getRules()['name'],
];
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class GetSubuserRequest extends ClientApiRequest
{
/**
* Confirm that a user is able to view subusers for the specified server.
*
* @return bool
*/
public function authorize(): bool
{
return $this->user()->can('user.read', $this->route()->parameter('server'));
}
}

View File

@ -1,71 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Requests\Base;
use Pterodactyl\Models\User;
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException;
class AccountDataFormRequest extends FrontendUserFormRequest
{
/**
* @return bool
* @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException
*/
public function authorize()
{
if (! parent::authorize()) {
return false;
}
// Verify password matches when changing password or email.
if (in_array($this->input('do_action'), ['password', 'email'])) {
if (! password_verify($this->input('current_password'), $this->user()->password)) {
throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password'));
}
}
return true;
}
/**
* @return array
*/
public function rules()
{
$modelRules = User::getRulesForUpdate($this->user());
switch ($this->input('do_action')) {
case 'email':
$rules = [
'new_email' => array_get($modelRules, 'email'),
];
break;
case 'password':
$rules = [
'new_password' => 'required|confirmed|string|min:8',
'new_password_confirmation' => 'required',
];
break;
case 'identity':
$rules = [
'name_first' => array_get($modelRules, 'name_first'),
'name_last' => array_get($modelRules, 'name_last'),
'username' => array_get($modelRules, 'username'),
'language' => array_get($modelRules, 'language'),
];
break;
default:
abort(422);
}
return $rules;
}
}

View File

@ -1,75 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Requests\Base;
use Exception;
use IPTools\Network;
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
class ApiKeyFormRequest extends FrontendUserFormRequest
{
/**
* Rules applied to data passed in this request.
*
* @return array
*/
public function rules()
{
$this->parseAllowedIntoArray();
return [
'memo' => 'required|nullable|string|max:500',
'permissions' => 'sometimes|present|array',
'admin_permissions' => 'sometimes|present|array',
'allowed_ips' => 'present',
'allowed_ips.*' => 'sometimes|string',
];
}
/**
* Parse the string of allowed IPs into an array.
*/
protected function parseAllowedIntoArray()
{
$loop = [];
if (! empty($this->input('allowed_ips'))) {
foreach (explode(PHP_EOL, $this->input('allowed_ips')) as $ip) {
$loop[] = trim($ip);
}
}
$this->merge(['allowed_ips' => $loop]);
}
/**
* Run additional validation rules on the request to ensure all of the data is good.
*
* @param \Illuminate\Validation\Validator $validator
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
/* @var \Illuminate\Validation\Validator $validator */
if (empty($this->input('permissions')) && empty($this->input('admin_permissions'))) {
$validator->errors()->add('permissions', 'At least one permission must be selected.');
}
foreach ($this->input('allowed_ips') as $ip) {
$ip = trim($ip);
try {
Network::parse($ip);
} catch (Exception $ex) {
$validator->errors()->add('allowed_ips', 'Could not parse IP ' . $ip . ' because it is in an invalid format.');
}
}
});
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Base;
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
class CreateClientApiKeyRequest extends FrontendUserFormRequest
{
/**
* Validate the data being provided.
*
* @return array
*/
public function rules()
{
return [
'memo' => 'required|string|max:255',
'allowed_ips' => 'nullable|string',
];
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Base;
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
class StoreAccountKeyRequest extends FrontendUserFormRequest
{
/**
* Rules to validate the request input against before storing
* an account API key.
*
* @return array
*/
public function rules()
{
return [
'memo' => 'required|nullable|string|max:500',
'allowed_ips' => 'present',
'allowed_ips.*' => 'sometimes|string',
];
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Server\Database;
use Pterodactyl\Http\Requests\Server\ServerFormRequest;
class DeleteServerDatabaseRequest extends ServerFormRequest
{
/**
* @return bool
*/
public function authorize()
{
if (! parent::authorize()) {
return false;
}
return config('pterodactyl.client_features.databases.enabled');
}
/**
* Return the user permission to validate this request against.
*
* @return string
*/
protected function permission(): string
{
return 'delete-database';
}
/**
* Rules to validate this request against.
*
* @return array
*/
public function rules()
{
return [];
}
}

View File

@ -1,43 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Server\Database;
use Pterodactyl\Http\Requests\Server\ServerFormRequest;
class StoreServerDatabaseRequest extends ServerFormRequest
{
/**
* @return bool
*/
public function authorize()
{
if (! parent::authorize()) {
return false;
}
return config('pterodactyl.client_features.databases.enabled');
}
/**
* Return the user permission to validate this request against.
*
* @return string
*/
protected function permission(): string
{
return 'create-database';
}
/**
* Rules to validate this request against.
*
* @return array
*/
public function rules()
{
return [
'database' => 'required|string|min:1',
'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/',
];
}
}

View File

@ -1,79 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Requests\Server;
class ScheduleCreationFormRequest extends ServerFormRequest
{
/**
* Permission to validate this request against.
*
* @return string
*/
protected function permission(): string
{
if ($this->method() === 'PATCH') {
return 'edit-schedule';
}
return 'create-schedule';
}
/**
* Validation rules to apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'nullable|string|max:255',
'cron_day_of_week' => 'required|string',
'cron_day_of_month' => 'required|string',
'cron_hour' => 'required|string',
'cron_minute' => 'required|string',
'tasks' => 'sometimes|array|size:4',
'tasks.time_value' => 'required_with:tasks|max:5',
'tasks.time_interval' => 'required_with:tasks|max:5',
'tasks.action' => 'required_with:tasks|max:5',
'tasks.payload' => 'required_with:tasks|max:5',
'tasks.time_value.*' => 'numeric|between:0,59',
'tasks.time_interval.*' => 'string|in:s,m',
'tasks.action.*' => 'string|in:power,command',
'tasks.payload.*' => 'string',
];
}
/**
* Normalize the request into a format that can be used by the application.
*
* @return array
*/
public function normalize()
{
return $this->only('name', 'cron_day_of_week', 'cron_day_of_month', 'cron_hour', 'cron_minute');
}
/**
* Return the tasks provided in the request that are associated with this schedule.
*
* @return array|null
*/
public function getTasks()
{
$restructured = [];
foreach (array_get($this->all(), 'tasks', []) as $key => $values) {
for ($i = 0; $i < count($values); $i++) {
$restructured[$i][$key] = $values[$i];
}
}
return empty($restructured) ? null : $restructured;
}
}

View File

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

View File

@ -1,31 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Server\Settings;
use Pterodactyl\Models\Server;
use Pterodactyl\Http\Requests\Server\ServerFormRequest;
class ChangeServerNameRequest extends ServerFormRequest
{
/**
* Permission to use when checking if a user can access this resource.
*
* @return string
*/
protected function permission(): string
{
return 'edit-name';
}
/**
* Rules to use when validating the submitted data.
*
* @return array
*/
public function rules()
{
return [
'name' => Server::getRules()['name'],
];
}
}

View File

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

View File

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

View File

@ -1,101 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Requests\Server;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Contracts\Config\Repository;
use Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface;
use Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
class UpdateFileContentsFormRequest extends ServerFormRequest
{
/**
* Return the permission string to validate this request against.
*
* @return string
*/
protected function permission(): string
{
return 'edit-files';
}
/**
* Authorize a request to edit a file.
*
* @return bool
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException
* @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function authorize()
{
if (! parent::authorize()) {
return false;
}
$server = $this->attributes->get('server');
$token = $this->attributes->get('server_token');
return $this->checkFileCanBeEdited($server, $token);
}
/**
* @return array
*/
public function rules()
{
return [];
}
/**
* Checks if a given file can be edited by a user on this server.
*
* @param \Pterodactyl\Models\Server $server
* @param string $token
* @return bool
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException
* @throws \Pterodactyl\Exceptions\Http\Server\FileTypeNotEditableException
*/
private function checkFileCanBeEdited($server, $token)
{
$config = app()->make(Repository::class);
$repository = app()->make(FileRepositoryInterface::class);
try {
$stats = $repository->setServer($server)->setToken($token)->getFileStat($this->route()->parameter('file'));
} catch (RequestException $exception) {
switch ($exception->getCode()) {
case 404:
throw new NotFoundHttpException;
default:
throw new DaemonConnectionException($exception);
}
}
if ((! $stats->file && ! $stats->symlink) || ! in_array($stats->mime, $config->get('pterodactyl.files.editable'))) {
throw new FileTypeNotEditableException(trans('server.files.exceptions.invalid_mime'));
}
if ($stats->size > $config->get('pterodactyl.files.max_edit_size')) {
throw new FileSizeTooLargeException(trans('server.files.exceptions.max_size'));
}
$this->attributes->set('file_stats', $stats);
return true;
}
}

View File

@ -1,61 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Server;
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
class UpdateStartupParametersFormRequest extends FrontendUserFormRequest
{
/**
* @var array
*/
private $validationAttributes = [];
/**
* Determine if the user has permission to update the startup parameters
* for this server.
*
* @return bool
*/
public function authorize()
{
if (! parent::authorize()) {
return false;
}
return $this->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;
}
}

View File

@ -30,5 +30,13 @@ class AssetComposer
public function compose(View $view)
{
$view->with('asset', $this->assetHashService);
$view->with('siteConfiguration', [
'name' => config('app.name') ?? 'Pterodactyl',
'locale' => config('app.locale') ?? 'en',
'recaptcha' => [
'enabled' => config('recaptcha.enabled', false),
'siteKey' => config('recaptcha.website_key') ?? '',
],
]);
}
}

View File

@ -2,6 +2,22 @@
namespace Pterodactyl\Models;
/**
* @property int $id
* @property int $node_id
* @property string $ip
* @property string|null $ip_alias
* @property int $port
* @property int|null $server_id
* @property \Carbon\Carbon|null $created_at
* @property \Carbon\Carbon|null $updated_at
*
* @property string $alias
* @property bool $has_alias
*
* @property \Pterodactyl\Models\Server|null $server
* @property \Pterodactyl\Models\Node $node
*/
class Allocation extends Validable
{
/**

View File

@ -2,6 +2,19 @@
namespace Pterodactyl\Models;
/**
* @property int $id
* @property string $uuid
* @property string $author
* @property string $name
* @property string $description
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
*
* @property \Illuminate\Support\Collection|\Pterodactyl\Models\Server[] $servers
* @property \Illuminate\Support\Collection|\Pterodactyl\Models\Egg[] $eggs
* @property \Illuminate\Support\Collection|\Pterodactyl\Models\Pack[] $packs
*/
class Nest extends Validable
{
/**

View File

@ -2,6 +2,7 @@
namespace Pterodactyl\Models;
use Symfony\Component\Yaml\Yaml;
use Illuminate\Notifications\Notifiable;
use Pterodactyl\Models\Traits\Searchable;
@ -151,71 +152,58 @@ class Node extends Validable
/**
* Returns the configuration in JSON format.
*
* @param bool $pretty
* @return string
*/
public function getConfigurationAsJson($pretty = false)
public function getYamlConfiguration()
{
$config = [
'web' => [
'debug' => false,
'api' => [
'host' => '0.0.0.0',
'listen' => $this->daemonListen,
'port' => $this->daemonListen,
'ssl' => [
'enabled' => (! $this->behind_proxy && $this->scheme === 'https'),
'certificate' => '/etc/letsencrypt/live/' . $this->fqdn . '/fullchain.pem',
'cert' => '/etc/letsencrypt/live/' . $this->fqdn . '/fullchain.pem',
'key' => '/etc/letsencrypt/live/' . $this->fqdn . '/privkey.pem',
],
'upload_limit' => $this->upload_size,
],
'system' => [
'data' => $this->daemonBase,
'username' => 'pterodactyl',
'timezone_path' => '/etc/timezone',
'set_permissions_on_boot' => true,
'detect_clean_exit_as_crash' => false,
'sftp' => [
'use_internal' => true,
'disable_disk_checking' => false,
'bind_address' => '0.0.0.0',
'bind_port' => $this->daemonSFTP,
'read_only' => false,
],
],
'docker' => [
'container' => [
'user' => null,
],
'network' => [
'interface' => '172.18.0.1',
'name' => 'pterodactyl_nw',
'driver' => 'bridge',
],
'update_images' => true,
'socket' => '/var/run/docker.sock',
'autoupdate_images' => true,
'timezone_path' => '/etc/timezone',
],
'filesystem' => [
'server_logs' => '/tmp/pterodactyl',
'disk_check_timeout' => 30,
'throttles' => [
'kill_at_count' => 5,
'decay' => 10,
'bytes' => 4096,
'check_interval' => 100,
],
'internals' => [
'disk_use_seconds' => 30,
'set_permissions_on_boot' => true,
'throttle' => [
'enabled' => true,
'kill_at_count' => 5,
'decay' => 10,
'lines' => 1000,
'check_interval_ms' => 100,
],
],
'sftp' => [
'path' => $this->daemonBase,
'ip' => '0.0.0.0',
'port' => $this->daemonSFTP,
'keypair' => [
'bits' => 2048,
'e' => 65537,
],
],
'logger' => [
'path' => 'logs/',
'src' => false,
'level' => 'info',
'period' => '1d',
'count' => 3,
],
'remote' => [
'base' => route('index'),
],
'uploads' => [
'size_limit' => $this->upload_size,
],
'keys' => [$this->daemonSecret],
'remote' => route('index'),
'token' => $this->daemonSecret,
];
return json_encode($config, ($pretty) ? JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_SLASHES);
return Yaml::dump($config, 4, 2);
}
/**

View File

@ -2,6 +2,8 @@
namespace Pterodactyl\Models;
use Illuminate\Support\Collection;
class Permission extends Validable
{
/**
@ -48,12 +50,130 @@ class Permission extends Validable
'permission' => 'required|string',
];
/**
* All of the permissions available on the system. You should use self::permissions()
* to retrieve them, and not directly access this array as it is subject to change.
*
* @var array
* @see \Pterodactyl\Models\Permission::permissions()
*/
protected static $permissions = [
'websocket' => [
// Allows the user to connect to the server websocket, this will give them
// access to view the console output as well as realtime server stats (CPU
// and Memory usage).
'*',
],
'control' => [
// Allows the user to send data to the server console process. A user with this
// permission will not be able to stop the server directly by issuing the specified
// stop command for the Egg, however depending on plugins and server configuration
// they may still be able to control the server power state.
'console', // power.send-command
// Allows the user to start/stop/restart/kill the server process.
'start', // power.power-start
'stop', // power.power-stop
'restart', // power.power-restart
'kill', // power.power-kill
],
'user' => [
// Allows a user to create a new user assigned to the server. They will not be able
// to assign any permissions they do not already have on their account as well.
'create', // subuser.create-subuser
'read', // subuser.list-subusers, subuser.view-subuser
'update', // subuser.edit-subuser
'delete', // subuser.delete-subuser
],
'file' => [
// Allows a user to create additional files and folders either via the Panel,
// or via a direct upload.
'create', // files.create-files, files.upload-files, files.copy-files, files.move-files
// Allows a user to view the contents of a directory as well as read the contents
// of a given file. A user with this permission will be able to download files
// as well.
'read', // files.list-files, files.download-files
// Allows a user to update the contents of an existing file or directory.
'update', // files.edit-files, files.save-files
// Allows a user to delete a file or directory.
'delete', // files.delete-files
// Allows a user to archive the contents of a directory as well as decompress existing
// archives on the system.
'archive', // files.compress-files, files.decompress-files
// Allows the user to connect and manage server files using their account
// credentials and a SFTP client.
'sftp', // files.access-sftp
],
// Controls permissions for editing or viewing a server's allocations.
'allocation' => [
'read', // server.view-allocations
'update', // server.edit-allocation
],
// Controls permissions for editing or viewing a server's startup parameters.
'startup' => [
'read', // server.view-startup
'update', // server.edit-startup
],
'database' => [
// Allows a user to create a new database for a server.
'create', // database.create-database
// Allows a user to view the databases associated with the server. If they do not also
// have the view_password permission they will only be able to see the connection address
// and the name of the user.
'read', // database.view-databases
// Allows a user to rotate the password on a database instance. If the user does not
// alow have the view_password permission they will not be able to see the updated password
// anywhere, but it will still be rotated.
'update', // database.reset-db-password
// Allows a user to delete a database instance.
'delete', // database.delete-database
// Allows a user to view the password associated with a database instance for the
// server. Note that a user without this permission may still be able to access these
// credentials by viewing files or the console.
'view_password', // database.reset-db-password
],
'schedule' => [
'create', // task.create-schedule
'read', // task.view-schedule, task.list-schedules
'update', // task.edit-schedule, task.queue-schedule, task.toggle-schedule
'delete', // task.delete-schedule
],
];
/**
* Returns all of the permissions available on the system for a user to
* have when controlling a server.
*
* @return \Illuminate\Support\Collection
*/
public static function permissions(): Collection
{
return Collection::make(self::$permissions);
}
/**
* A list of all permissions available for a user.
*
* @var array
* @deprecated
*/
protected static $permissions = [
protected static $deprecatedPermissions = [
'power' => [
'power-start' => 's:power:start',
'power-stop' => 's:power:stop',
@ -110,16 +230,17 @@ class Permission extends Validable
*
* @param bool $array
* @return array|\Illuminate\Support\Collection
* @deprecated
*/
public static function getPermissions($array = false)
{
if ($array) {
return collect(self::$permissions)->mapWithKeys(function ($item) {
return collect(self::$deprecatedPermissions)->mapWithKeys(function ($item) {
return $item;
})->all();
}
return collect(self::$permissions);
return collect(self::$deprecatedPermissions);
}
/**

View File

@ -16,7 +16,7 @@ use Znck\Eloquent\Traits\BelongsToThrough;
* @property string $name
* @property string $description
* @property bool $skip_scripts
* @property bool $suspended
* @property int $suspended
* @property int $owner_id
* @property int $memory
* @property int $swap
@ -44,7 +44,7 @@ use Znck\Eloquent\Traits\BelongsToThrough;
* @property \Pterodactyl\Models\Node $node
* @property \Pterodactyl\Models\Nest $nest
* @property \Pterodactyl\Models\Egg $egg
* @property \Pterodactyl\Models\EggVariable[]|\Illuminate\Support\Collection $variables
* @property \Pterodactyl\Models\ServerVariable[]|\Illuminate\Support\Collection $variables
* @property \Pterodactyl\Models\Schedule[]|\Illuminate\Support\Collection $schedule
* @property \Pterodactyl\Models\Database[]|\Illuminate\Support\Collection $databases
* @property \Pterodactyl\Models\Location $location
@ -170,6 +170,18 @@ class Server extends Validable
return Schema::getColumnListing($this->getTable());
}
/**
* Returns the format for server allocations when communicating with the Daemon.
*
* @return array
*/
public function getAllocationMappings(): array
{
return $this->allocations->groupBy('ip')->map(function ($item) {
return $item->pluck('port');
})->toArray();
}
/**
* @return bool
*/

View File

@ -4,6 +4,17 @@ namespace Pterodactyl\Models;
use Illuminate\Notifications\Notifiable;
/**
* @property int $id
* @property int $user_id
* @property int $server_id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
*
* @property \Pterodactyl\Models\User $user
* @property \Pterodactyl\Models\Server $server
* @property \Pterodactyl\Models\Permission[]|\Illuminate\Support\Collection $permissions
*/
class Subuser extends Validable
{
use Notifiable;

View File

@ -158,7 +158,7 @@ class User extends Validable implements
'username' => 'required|between:1,255|unique:users,username',
'name_first' => 'required|string|between:1,255',
'name_last' => 'required|string|between:1,255',
'password' => 'required|nullable|string',
'password' => 'sometimes|nullable|string',
'root_admin' => 'boolean',
'language' => 'required|string',
'use_totp' => 'boolean',
@ -171,7 +171,7 @@ class User extends Validable implements
*/
public static function getRules()
{
$rules = self::getRules();
$rules = parent::getRules();
$rules['language'][] = new In(array_keys((new self)->getAvailableLanguages()));
$rules['username'][] = new Username;
@ -216,7 +216,7 @@ class User extends Validable implements
*/
public function getNameAttribute()
{
return $this->name_first . ' ' . $this->name_last;
return trim($this->name_first . ' ' . $this->name_last);
}
/**

View File

@ -31,27 +31,31 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
*/
public function getUsageStats(Node $node): array
{
$stats = $this->getBuilder()->select(
$this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
)->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first();
$stats = $this->getBuilder()
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
->join('servers', 'servers.node_id', '=', 'nodes.id')
->where('node_id', '=', $node->id)
->first();
return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) {
$maxUsage = $node->{$key};
if ($node->{$key . '_overallocate'} > 0) {
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
}
return Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])
->mapWithKeys(function ($value, $key) use ($node) {
$maxUsage = $node->{$key};
if ($node->{$key . '_overallocate'} > 0) {
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
}
$percent = ($value / $maxUsage) * 100;
$percent = ($value / $maxUsage) * 100;
return [
$key => [
'value' => number_format($value),
'max' => number_format($maxUsage),
'percent' => $percent,
'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'),
],
];
})->toArray();
return [
$key => [
'value' => number_format($value),
'max' => number_format($maxUsage),
'percent' => $percent,
'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'),
],
];
})
->toArray();
}
/**
@ -132,7 +136,12 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
public function loadNodeAllocations(Node $node, bool $refresh = false): Node
{
$node->setRelation('allocations',
$node->allocations()->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')->orderByRaw('INET_ATON(ip) ASC')->orderBy('port', 'asc')->with('server:id,name')->paginate(50)
$node->allocations()
->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')
->orderByRaw('INET_ATON(ip) ASC')
->orderBy('port', 'asc')
->with('server:id,name')
->paginate(50)
);
return $node;

View File

@ -3,9 +3,9 @@
namespace Pterodactyl\Repositories\Eloquent;
use Pterodactyl\Models\User;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Server;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Builder;
use Pterodactyl\Repositories\Concerns\Searchable;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
@ -273,12 +273,16 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
*/
public function getByUuid(string $uuid): Server
{
Assert::notEmpty($uuid, 'Expected non-empty string as first argument passed to ' . __METHOD__);
try {
return $this->getBuilder()->with('nest', 'node')->where(function ($query) use ($uuid) {
$query->where('uuidShort', $uuid)->orWhere('uuid', $uuid);
})->firstOrFail($this->getColumns());
/** @var \Pterodactyl\Models\Server $model */
$model = $this->getBuilder()
->with('nest', 'node')
->where(function (Builder $query) use ($uuid) {
$query->where('uuidShort', $uuid)->orWhere('uuid', $uuid);
})
->firstOrFail($this->getColumns());
return $model;
} catch (ModelNotFoundException $exception) {
throw new RecordNotFoundException;
}

View File

@ -3,6 +3,7 @@
namespace Pterodactyl\Repositories\Eloquent;
use Pterodactyl\Models\Subuser;
use Illuminate\Support\Collection;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
@ -18,6 +19,22 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI
return Subuser::class;
}
/**
* Returns the subusers for the given server instance with the associated user
* and permission relationships pre-loaded.
*
* @param int $server
* @return \Illuminate\Support\Collection
*/
public function getSubusersForServer(int $server): Collection
{
return $this->getBuilder()
->with('user', 'permissions')
->where('server_id', $server)
->get()
->toBase();
}
/**
* Return a subuser with the associated server relationship.
*

View File

@ -2,6 +2,25 @@
namespace Pterodactyl\Repositories\Wings;
use GuzzleHttp\Exception\TransferException;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
class DaemonConfigurationRepository extends DaemonRepository
{
/**
* Returns system information from the wings instance.
*
* @return array
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function getSystemInformation(): array
{
try {
$response = $this->getHttpClient()->get('/api/system');
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
return json_decode($response->getBody()->__toString(), true);
}
}

View File

@ -57,6 +57,29 @@ class DaemonFileRepository extends DaemonRepository
return $response->getBody()->__toString();
}
/**
* Returns a stream of a file's contents back to the calling function to allow
* proxying the request through the Panel rather than needing a direct call to
* the Daemon in order to work.
*
* @param string $path
* @return \Psr\Http\Message\ResponseInterface
*/
public function streamContent(string $path): ResponseInterface
{
Assert::isInstanceOf($this->server, Server::class);
$response = $this->getHttpClient()->get(
sprintf('/api/servers/%s/files/contents', $this->server->uuid),
[
'query' => ['file' => $path, 'download' => true],
'stream' => true,
]
);
return $response;
}
/**
* Save new contents to a given file. This works for both creating and updating
* a file.

View File

@ -2,6 +2,7 @@
namespace Pterodactyl\Repositories\Wings;
use BadMethodCallException;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Server;
use GuzzleHttp\Exception\TransferException;
@ -29,4 +30,90 @@ class DaemonServerRepository extends DaemonRepository
return json_decode($response->getBody()->__toString(), true);
}
/**
* Creates a new server on the Wings daemon.
*
* @param array $data
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function create(array $data): void
{
Assert::isInstanceOf($this->server, Server::class);
try {
$this->getHttpClient()->post(
'/api/servers', [
'json' => $data,
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
}
/**
* Updates details about a server on the Daemon.
*
* @param array $data
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function update(array $data): void
{
Assert::isInstanceOf($this->server, Server::class);
try {
$this->getHttpClient()->patch('/api/servers/' . $this->server->uuid, ['json' => $data]);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
}
/**
* Delete a server from the daemon, forcibly if passed.
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function delete(): void
{
Assert::isInstanceOf($this->server, Server::class);
try {
$this->getHttpClient()->delete('/api/servers/' . $this->server->uuid);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
}
/**
* Reinstall a server on the daemon.
*/
public function reinstall(): void
{
throw new BadMethodCallException('Method is not implemented.');
}
/**
* By default this function will suspend a server instance on the daemon. However, passing
* "true" as the first argument will unsuspend the server.
*
* @param bool $unsuspend
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function suspend(bool $unsuspend = false): void
{
Assert::isInstanceOf($this->server, Server::class);
try {
$this->getHttpClient()->patch(
'/api/servers/' . $this->server->uuid,
['json' => ['suspended' => ! $unsuspend]]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
}
}

View File

@ -1,124 +0,0 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Services\DaemonKeys;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Server;
use Psr\Log\LoggerInterface as Writer;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
class DaemonKeyDeletionService
{
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected $connection;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
*/
protected $daemonRepository;
/**
* @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $serverRepository;
/**
* @var \Psr\Log\LoggerInterface
*/
protected $writer;
/**
* DaemonKeyDeletionService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository
* @param \Psr\Log\LoggerInterface $writer
*/
public function __construct(
ConnectionInterface $connection,
DaemonKeyRepositoryInterface $repository,
DaemonServerRepositoryInterface $daemonRepository,
ServerRepositoryInterface $serverRepository,
Writer $writer
) {
$this->connection = $connection;
$this->daemonRepository = $daemonRepository;
$this->repository = $repository;
$this->serverRepository = $serverRepository;
$this->writer = $writer;
}
/**
* @param \Pterodactyl\Models\Server|int $server
* @param int $user
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle($server, $user)
{
Assert::integerish($user, 'Second argument passed to handle must be an integer, received %s.');
if (! $server instanceof Server) {
$server = $this->serverRepository->find($server);
}
$this->connection->beginTransaction();
$key = $this->repository->findFirstWhere([
['user_id', '=', $user],
['server_id', '=', $server->id],
]);
$this->repository->delete($key->id);
try {
$this->daemonRepository->setServer($server)->revokeAccessKey($key->secret);
} catch (RequestException $exception) {
$response = $exception->getResponse();
$this->connection->rollBack();
$this->writer->warning($exception);
throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [
'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(),
]));
}
$this->connection->commit();
}
}

View File

@ -107,7 +107,7 @@ class EggConfigurationService
{
// Get the legacy configuration structure for the server so that we
// can property map the egg placeholders to values.
$structure = $this->configurationStructureService->handle($server);
$structure = $this->configurationStructureService->handle($server, true);
foreach ($configs as $file => $data) {
foreach ($data->find ?? [] as &$value) {

View File

@ -2,10 +2,9 @@
namespace Pterodactyl\Services\Helpers;
use Cake\Chronos\Chronos;
use Illuminate\Support\Arr;
use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
class AssetHashService
{
@ -14,11 +13,6 @@ class AssetHashService
*/
public const MANIFEST_PATH = './assets/manifest.json';
/**
* @var \Illuminate\Contracts\Cache\Repository
*/
private $cache;
/**
* @var \Illuminate\Contracts\Filesystem\Filesystem
*/
@ -38,13 +32,11 @@ class AssetHashService
* AssetHashService constructor.
*
* @param \Illuminate\Contracts\Foundation\Application $application
* @param \Illuminate\Contracts\Cache\Repository $cache
* @param \Illuminate\Filesystem\FilesystemManager $filesystem
*/
public function __construct(Application $application, CacheRepository $cache, FilesystemManager $filesystem)
public function __construct(Application $application, FilesystemManager $filesystem)
{
$this->application = $application;
$this->cache = $cache;
$this->filesystem = $filesystem->createLocalDriver(['root' => public_path()]);
}
@ -59,9 +51,9 @@ class AssetHashService
public function url(string $resource): string
{
$file = last(explode('/', $resource));
$data = array_get($this->manifest(), $file, $file);
$data = Arr::get($this->manifest(), $file) ?? $file;
return str_replace($file, array_get($data, 'src', $file), $resource);
return str_replace($file, Arr::get($data, 'src') ?? $file, $resource);
}
/**
@ -77,7 +69,7 @@ class AssetHashService
$file = last(explode('/', $resource));
$data = array_get($this->manifest(), $file, $file);
return array_get($data, 'integrity', '');
return Arr::get($data, 'integrity') ?? '';
}
/**
@ -122,21 +114,8 @@ class AssetHashService
*/
protected function manifest(): array
{
if (! is_null(self::$manifest)) {
return self::$manifest;
}
// Skip checking the cache if we are not in production.
if ($this->application->environment() === 'production') {
$stored = $this->cache->get('Core:AssetManifest');
if (! is_null($stored)) {
return self::$manifest = $stored;
}
}
$contents = json_decode($this->filesystem->get(self::MANIFEST_PATH), true);
$this->cache->put('Core:AssetManifest', $contents, Chronos::now()->addMinutes(1440));
return self::$manifest = $contents;
return self::$manifest ?: self::$manifest = json_decode(
$this->filesystem->get(self::MANIFEST_PATH), true
);
}
}

View File

@ -1,25 +1,22 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Services\Helpers;
use stdClass;
use Exception;
use GuzzleHttp\Client;
use Cake\Chronos\Chronos;
use Illuminate\Support\Arr;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException;
class SoftwareVersionService
{
const VERSION_CACHE_KEY = 'pterodactyl:versions';
const VERSION_CACHE_KEY = 'pterodactyl:versioning_data';
/**
* @var array
*/
private static $result;
/**
* @var \Illuminate\Contracts\Cache\Repository
@ -31,28 +28,20 @@ class SoftwareVersionService
*/
protected $client;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
protected $config;
/**
* SoftwareVersionService constructor.
*
* @param \Illuminate\Contracts\Cache\Repository $cache
* @param \GuzzleHttp\Client $client
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(
CacheRepository $cache,
Client $client,
ConfigRepository $config
Client $client
) {
$this->cache = $cache;
$this->client = $client;
$this->config = $config;
$this->cacheVersionData();
self::$result = $this->cacheVersionData();
}
/**
@ -62,7 +51,7 @@ class SoftwareVersionService
*/
public function getPanel()
{
return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'panel', 'error');
return Arr::get(self::$result, 'panel') ?? 'error';
}
/**
@ -72,7 +61,7 @@ class SoftwareVersionService
*/
public function getDaemon()
{
return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'daemon', 'error');
return Arr::get(self::$result, 'daemon') ?? 'error';
}
/**
@ -82,7 +71,17 @@ class SoftwareVersionService
*/
public function getDiscord()
{
return object_get($this->cache->get(self::VERSION_CACHE_KEY), 'discord', 'https://pterodactyl.io/discord');
return Arr::get(self::$result, 'discord') ?? 'https://pterodactyl.io/discord';
}
/**
* Get the URL for donations.
*
* @return string
*/
public function getDonations()
{
return Arr::get(self::$result, 'donations') ?? 'https://paypal.me/PterodactylSoftware';
}
/**
@ -92,11 +91,11 @@ class SoftwareVersionService
*/
public function isLatestPanel()
{
if ($this->config->get('app.version') === 'canary') {
if (config()->get('app.version') === 'canary') {
return true;
}
return version_compare($this->config->get('app.version'), $this->getPanel()) >= 0;
return version_compare(config()->get('app.version'), $this->getPanel()) >= 0;
}
/**
@ -116,20 +115,22 @@ class SoftwareVersionService
/**
* Keeps the versioning cache up-to-date with the latest results from the CDN.
*
* @return array
*/
protected function cacheVersionData()
{
$this->cache->remember(self::VERSION_CACHE_KEY, Chronos::now()->addMinutes(config('pterodactyl.cdn.cache_time')), function () {
return $this->cache->remember(self::VERSION_CACHE_KEY, Chronos::now()->addMinutes(config()->get('pterodactyl.cdn.cache_time', 60)), function () {
try {
$response = $this->client->request('GET', $this->config->get('pterodactyl.cdn.url'));
$response = $this->client->request('GET', config()->get('pterodactyl.cdn.url'));
if ($response->getStatusCode() === 200) {
return json_decode($response->getBody());
return json_decode($response->getBody(), true);
}
throw new CdnVersionFetchingException;
} catch (Exception $exception) {
return new stdClass();
return [];
}
});
}

View File

@ -6,10 +6,10 @@ use Pterodactyl\Models\Node;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Repositories\Daemon\ConfigurationRepository;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException;
use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface;
class NodeUpdateService
{
@ -32,12 +32,12 @@ class NodeUpdateService
* UpdateService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface $configurationRepository
* @param \Pterodactyl\Repositories\Daemon\ConfigurationRepository $configurationRepository
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
*/
public function __construct(
ConnectionInterface $connection,
ConfigurationRepositoryInterface $configurationRepository,
ConfigurationRepository $configurationRepository,
NodeRepositoryInterface $repository
) {
$this->connection = $connection;
@ -58,6 +58,8 @@ class NodeUpdateService
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException
*
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function handle(Node $node, array $data, bool $resetToken = false)
{

View File

@ -2,15 +2,16 @@
namespace Pterodactyl\Services\Servers;
use Illuminate\Support\Arr;
use Pterodactyl\Models\Server;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
class BuildModificationService
{
@ -25,7 +26,7 @@ class BuildModificationService
private $connection;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
* @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
*/
private $daemonServerRepository;
@ -34,24 +35,32 @@ class BuildModificationService
*/
private $repository;
/**
* @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService
*/
private $structureService;
/**
* BuildModificationService constructor.
*
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
* @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $structureService
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository
* @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(
AllocationRepositoryInterface $allocationRepository,
ServerConfigurationStructureService $structureService,
ConnectionInterface $connection,
DaemonServerRepositoryInterface $daemonServerRepository,
DaemonServerRepository $daemonServerRepository,
ServerRepositoryInterface $repository
) {
$this->allocationRepository = $allocationRepository;
$this->daemonServerRepository = $daemonServerRepository;
$this->connection = $connection;
$this->repository = $repository;
$this->structureService = $structureService;
}
/**
@ -67,23 +76,22 @@ class BuildModificationService
*/
public function handle(Server $server, array $data)
{
$build = [];
$this->connection->beginTransaction();
$this->processAllocations($server, $data);
if (isset($data['allocation_id']) && $data['allocation_id'] != $server->allocation_id) {
try {
$allocation = $this->allocationRepository->findFirstWhere([
$this->allocationRepository->findFirstWhere([
['id', '=', $data['allocation_id']],
['server_id', '=', $server->id],
]);
} catch (RecordNotFoundException $ex) {
throw new DisplayException(trans('admin/server.exceptions.default_allocation_not_found'));
}
$build['default'] = ['ip' => $allocation->ip, 'port' => $allocation->port];
}
/** @var \Pterodactyl\Models\Server $server */
$server = $this->repository->withFreshModel()->update($server->id, [
'oom_disabled' => array_get($data, 'oom_disabled'),
'memory' => array_get($data, 'memory'),
@ -96,20 +104,13 @@ class BuildModificationService
'allocation_limit' => array_get($data, 'allocation_limit'),
]);
$allocations = $this->allocationRepository->findWhere([['server_id', '=', $server->id]]);
$build['oom_disabled'] = $server->oom_disabled;
$build['memory'] = (int) $server->memory;
$build['swap'] = (int) $server->swap;
$build['io'] = (int) $server->io;
$build['cpu'] = (int) $server->cpu;
$build['disk'] = (int) $server->disk;
$build['ports|overwrite'] = $allocations->groupBy('ip')->map(function ($item) {
return $item->pluck('port');
})->toArray();
$updateData = $this->structureService->handle($server);
try {
$this->daemonServerRepository->setServer($server)->update(['build' => $build]);
$this->daemonServerRepository
->setServer($server)
->update(Arr::only($updateData, ['build']));
$this->connection->commit();
} catch (RequestException $exception) {
throw new DaemonConnectionException($exception);

View File

@ -1,42 +0,0 @@
<?php
namespace Pterodactyl\Services\Servers;
use Pterodactyl\Models\Server;
use GuzzleHttp\Exception\RequestException;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface;
class ContainerRebuildService
{
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
private $repository;
/**
* ContainerRebuildService constructor.
*
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $repository
*/
public function __construct(ServerRepositoryInterface $repository)
{
$this->repository = $repository;
}
/**
* Mark a server for rebuild on next boot cycle.
*
* @param \Pterodactyl\Models\Server $server
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function handle(Server $server)
{
try {
$this->repository->setServer($server)->rebuild();
} catch (RequestException $exception) {
throw new DaemonConnectionException($exception);
}
}
}

View File

@ -6,8 +6,6 @@ use Pterodactyl\Models\Server;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Traits\Services\ReturnsUpdatedModels;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService;
use Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService;
class DetailsModificationService
{
@ -18,16 +16,6 @@ class DetailsModificationService
*/
private $connection;
/**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService
*/
private $keyCreationService;
/**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService
*/
private $keyDeletionService;
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
@ -37,19 +25,13 @@ class DetailsModificationService
* DetailsModificationService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService $keyDeletionService
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
*/
public function __construct(
ConnectionInterface $connection,
DaemonKeyCreationService $keyCreationService,
DaemonKeyDeletionService $keyDeletionService,
ServerRepository $repository
) {
$this->connection = $connection;
$this->keyCreationService = $keyCreationService;
$this->keyDeletionService = $keyDeletionService;
$this->repository = $repository;
}
@ -60,7 +42,6 @@ class DetailsModificationService
* @param array $data
* @return bool|\Pterodactyl\Models\Server
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
@ -75,11 +56,6 @@ class DetailsModificationService
'description' => array_get($data, 'description') ?? '',
], true, true);
if ((int) array_get($data, 'owner_id', 0) !== (int) $server->owner_id) {
$this->keyDeletionService->handle($server, $server->owner_id);
$this->keyCreationService->handle($server->id, array_get($data, 'owner_id'));
}
$this->connection->commit();
return $response;

View File

@ -1,48 +1,41 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Services\Servers;
use Pterodactyl\Models\Server;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
class ReinstallServerService
{
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
* @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
*/
protected $daemonServerRepository;
private $daemonServerRepository;
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected $database;
private $database;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $repository;
private $repository;
/**
* ReinstallService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $database
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository
* @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(
ConnectionInterface $database,
DaemonServerRepositoryInterface $daemonServerRepository,
DaemonServerRepository $daemonServerRepository,
ServerRepositoryInterface $repository
) {
$this->daemonServerRepository = $daemonServerRepository;

View File

@ -47,14 +47,73 @@ class ServerConfigurationStructureService
* daemon, if you modify the structure eggs will break unexpectedly.
*
* @param \Pterodactyl\Models\Server $server
* @param bool $legacy
* @return array
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle(Server $server): array
public function handle(Server $server, bool $legacy = false): array
{
$server->loadMissing(self::REQUIRED_RELATIONS);
return $legacy ?
$this->returnLegacyFormat($server)
: $this->returnCurrentFormat($server);
}
/**
* Returns the new data format used for the Wings daemon.
*
* @param \Pterodactyl\Models\Server $server
* @return array
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
protected function returnCurrentFormat(Server $server)
{
return [
'uuid' => $server->uuid,
'suspended' => (bool) $server->suspended,
'environment' => $this->environment->handle($server),
'invocation' => $server->startup,
'build' => [
'memory_limit' => $server->memory,
'swap' => $server->swap,
'io_weight' => $server->io,
'cpu_limit' => $server->cpu,
'disk_space' => $server->disk,
],
'service' => [
'egg' => $server->egg->uuid,
'pack' => $server->pack ? $server->pack->uuid : null,
'skip_scripts' => $server->skip_scripts,
],
'container' => [
'image' => $server->image,
'oom_disabled' => $server->oom_disabled,
'requires_rebuild' => false,
],
'allocations' => [
'default' => [
'ip' => $server->allocation->ip,
'port' => $server->allocation->port,
],
'mappings' => $server->getAllocationMappings(),
],
];
}
/**
* Returns the legacy server data format to continue support for old egg configurations
* that have not yet been updated.
*
* @param \Pterodactyl\Models\Server $server
* @return array
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
protected function returnLegacyFormat(Server $server)
{
return [
'uuid' => $server->uuid,
'build' => [

View File

@ -3,27 +3,27 @@
namespace Pterodactyl\Services\Servers;
use Ramsey\Uuid\Uuid;
use Illuminate\Support\Arr;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
use Illuminate\Support\Collection;
use Pterodactyl\Models\Allocation;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Models\Objects\DeploymentObject;
use Pterodactyl\Repositories\Eloquent\EggRepository;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Repositories\Eloquent\AllocationRepository;
use Pterodactyl\Services\Deployment\FindViableNodesService;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\ServerVariableRepository;
use Pterodactyl\Services\Deployment\AllocationSelectionService;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
class ServerCreationService
{
/**
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
* @var \Pterodactyl\Repositories\Eloquent\AllocationRepository
*/
private $allocationRepository;
@ -42,72 +42,80 @@ class ServerCreationService
*/
private $connection;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
*/
private $daemonServerRepository;
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface
*/
private $eggRepository;
/**
* @var \Pterodactyl\Services\Deployment\FindViableNodesService
*/
private $findViableNodesService;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
private $repository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface
*/
private $serverVariableRepository;
/**
* @var \Pterodactyl\Services\Servers\VariableValidatorService
*/
private $validatorService;
/**
* @var \Pterodactyl\Repositories\Eloquent\EggRepository
*/
private $eggRepository;
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
private $repository;
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerVariableRepository
*/
private $serverVariableRepository;
/**
* @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
*/
private $daemonServerRepository;
/**
* @var \Pterodactyl\Services\Servers\ServerDeletionService
*/
private $serverDeletionService;
/**
* CreationService constructor.
*
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
* @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $allocationRepository
* @param \Pterodactyl\Services\Deployment\AllocationSelectionService $allocationSelectionService
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository
* @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $eggRepository
* @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
* @param \Pterodactyl\Repositories\Eloquent\EggRepository $eggRepository
* @param \Pterodactyl\Services\Deployment\FindViableNodesService $findViableNodesService
* @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository
* @param \Pterodactyl\Services\Servers\ServerDeletionService $serverDeletionService
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
* @param \Pterodactyl\Repositories\Eloquent\ServerVariableRepository $serverVariableRepository
* @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService
*/
public function __construct(
AllocationRepositoryInterface $allocationRepository,
AllocationRepository $allocationRepository,
AllocationSelectionService $allocationSelectionService,
ConnectionInterface $connection,
DaemonServerRepositoryInterface $daemonServerRepository,
EggRepositoryInterface $eggRepository,
DaemonServerRepository $daemonServerRepository,
EggRepository $eggRepository,
FindViableNodesService $findViableNodesService,
ServerConfigurationStructureService $configurationStructureService,
ServerRepositoryInterface $repository,
ServerVariableRepositoryInterface $serverVariableRepository,
ServerDeletionService $serverDeletionService,
ServerRepository $repository,
ServerVariableRepository $serverVariableRepository,
VariableValidatorService $validatorService
) {
$this->allocationSelectionService = $allocationSelectionService;
$this->allocationRepository = $allocationRepository;
$this->configurationStructureService = $configurationStructureService;
$this->connection = $connection;
$this->daemonServerRepository = $daemonServerRepository;
$this->eggRepository = $eggRepository;
$this->findViableNodesService = $findViableNodesService;
$this->validatorService = $validatorService;
$this->eggRepository = $eggRepository;
$this->repository = $repository;
$this->serverVariableRepository = $serverVariableRepository;
$this->validatorService = $validatorService;
$this->daemonServerRepository = $daemonServerRepository;
$this->serverDeletionService = $serverDeletionService;
}
/**
@ -120,12 +128,12 @@ class ServerCreationService
* @param \Pterodactyl\Models\Objects\DeploymentObject|null $deployment
* @return \Pterodactyl\Models\Server
*
* @throws \Throwable
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
*/
public function handle(array $data, DeploymentObject $deployment = null): Server
@ -142,35 +150,41 @@ class ServerCreationService
// Auto-configure the node based on the selected allocation
// if no node was defined.
if (is_null(array_get($data, 'node_id'))) {
if (is_null(Arr::get($data, 'node_id'))) {
$data['node_id'] = $this->getNodeFromAllocation($data['allocation_id']);
}
if (is_null(array_get($data, 'nest_id'))) {
$egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find(array_get($data, 'egg_id'));
if (is_null(Arr::get($data, 'nest_id'))) {
/** @var \Pterodactyl\Models\Egg $egg */
$egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find(Arr::get($data, 'egg_id'));
$data['nest_id'] = $egg->nest_id;
}
$eggVariableData = $this->validatorService
->setUserLevel(User::USER_LEVEL_ADMIN)
->handle(array_get($data, 'egg_id'), array_get($data, 'environment', []));
->handle(Arr::get($data, 'egg_id'), Arr::get($data, 'environment', []));
// Create the server and assign any additional allocations to it.
$server = $this->createModel($data);
$this->storeAssignedAllocations($server, $data);
$this->storeEggVariables($server, $eggVariableData);
// Due to the design of the Daemon, we need to persist this server to the disk
// before we can actually create it on the Daemon.
//
// If that connection fails out we will attempt to perform a cleanup by just
// deleting the server itself from the system.
$this->connection->commit();
$structure = $this->configurationStructureService->handle($server);
try {
$this->daemonServerRepository->setServer($server)->create($structure, [
'start_on_completion' => (bool) array_get($data, 'start_on_completion', false),
]);
$this->daemonServerRepository->setServer($server)->create($structure);
} catch (DaemonConnectionException $exception) {
$this->serverDeletionService->withForce(true)->handle($server);
$this->connection->commit();
} catch (RequestException $exception) {
$this->connection->rollBack();
throw new DaemonConnectionException($exception);
throw $exception;
}
return $server;
@ -190,8 +204,8 @@ class ServerCreationService
private function configureDeployment(array $data, DeploymentObject $deployment): Allocation
{
$nodes = $this->findViableNodesService->setLocations($deployment->getLocations())
->setDisk(array_get($data, 'disk'))
->setMemory(array_get($data, 'memory'))
->setDisk(Arr::get($data, 'disk'))
->setMemory(Arr::get($data, 'memory'))
->handle();
return $this->allocationSelectionService->setDedicated($deployment->isDedicated())
@ -212,32 +226,34 @@ class ServerCreationService
{
$uuid = $this->generateUniqueUuidCombo();
return $this->repository->create([
'external_id' => array_get($data, 'external_id'),
/** @var \Pterodactyl\Models\Server $model */
$model = $this->repository->create([
'external_id' => Arr::get($data, 'external_id'),
'uuid' => $uuid,
'uuidShort' => substr($uuid, 0, 8),
'node_id' => array_get($data, 'node_id'),
'name' => array_get($data, 'name'),
'description' => array_get($data, 'description') ?? '',
'skip_scripts' => array_get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
'node_id' => Arr::get($data, 'node_id'),
'name' => Arr::get($data, 'name'),
'description' => Arr::get($data, 'description') ?? '',
'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
'suspended' => false,
'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' => array_get($data, 'oom_disabled', true),
'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' => array_get($data, 'startup'),
'daemonSecret' => str_random(Node::DAEMON_SECRET_LENGTH),
'image' => array_get($data, 'image'),
'database_limit' => array_get($data, 'database_limit'),
'allocation_limit' => array_get($data, 'allocation_limit'),
'owner_id' => Arr::get($data, 'owner_id'),
'memory' => Arr::get($data, 'memory'),
'swap' => Arr::get($data, 'swap'),
'disk' => Arr::get($data, 'disk'),
'io' => Arr::get($data, 'io'),
'cpu' => Arr::get($data, 'cpu'),
'oom_disabled' => Arr::get($data, 'oom_disabled', true),
'allocation_id' => Arr::get($data, 'allocation_id'),
'nest_id' => Arr::get($data, 'nest_id'),
'egg_id' => Arr::get($data, 'egg_id'),
'pack_id' => empty($data['pack_id']) ? null : $data['pack_id'],
'startup' => Arr::get($data, 'startup'),
'image' => Arr::get($data, 'image'),
'database_limit' => Arr::get($data, 'database_limit'),
'allocation_limit' => Arr::get($data, 'allocation_limit'),
]);
return $model;
}
/**
@ -280,18 +296,21 @@ class ServerCreationService
/**
* Get the node that an allocation belongs to.
*
* @param int $allocation
* @param int $id
* @return int
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
private function getNodeFromAllocation(int $allocation): int
private function getNodeFromAllocation(int $id): int
{
$allocation = $this->allocationRepository->setColumns(['id', 'node_id'])->find($allocation);
/** @var \Pterodactyl\Models\Allocation $allocation */
$allocation = $this->allocationRepository->setColumns(['id', 'node_id'])->find($id);
return $allocation->node_id;
}
/** @noinspection PhpDocMissingThrowsInspection */
/**
* Create a unique UUID and UUID-Short combo for a server.
*
@ -299,6 +318,7 @@ class ServerCreationService
*/
private function generateUniqueUuidCombo(): string
{
/** @noinspection PhpUnhandledExceptionInspection */
$uuid = Uuid::uuid4()->toString();
if (! $this->repository->isUniqueUuidCombo($uuid, substr($uuid, 0, 8))) {

View File

@ -1,82 +1,75 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Services\Servers;
use Psr\Log\LoggerInterface as Writer;
use GuzzleHttp\Exception\RequestException;
use Psr\Log\LoggerInterface;
use Pterodactyl\Models\Server;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Repositories\Eloquent\DatabaseRepository;
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Services\Databases\DatabaseManagementService;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
class ServerDeletionService
{
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected $connection;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
*/
protected $daemonServerRepository;
/**
* @var \Pterodactyl\Services\Databases\DatabaseManagementService
*/
protected $databaseManagementService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
*/
protected $databaseRepository;
/**
* @var bool
*/
protected $force = false;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
* @var \Illuminate\Database\ConnectionInterface
*/
protected $repository;
private $connection;
/**
* @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
*/
private $daemonServerRepository;
/**
* @var \Pterodactyl\Repositories\Eloquent\DatabaseRepository
*/
private $databaseRepository;
/**
* @var \Pterodactyl\Services\Databases\DatabaseManagementService
*/
private $databaseManagementService;
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
private $repository;
/**
* @var \Psr\Log\LoggerInterface
*/
protected $writer;
private $writer;
/**
* DeletionService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository
* @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
* @param \Pterodactyl\Repositories\Eloquent\DatabaseRepository $databaseRepository
* @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
* @param \Psr\Log\LoggerInterface $writer
*/
public function __construct(
ConnectionInterface $connection,
DaemonServerRepositoryInterface $daemonServerRepository,
DatabaseRepositoryInterface $databaseRepository,
DaemonServerRepository $daemonServerRepository,
DatabaseRepository $databaseRepository,
DatabaseManagementService $databaseManagementService,
ServerRepositoryInterface $repository,
Writer $writer
ServerRepository $repository,
LoggerInterface $writer
) {
$this->daemonServerRepository = $daemonServerRepository;
$this->connection = $connection;
$this->databaseManagementService = $databaseManagementService;
$this->daemonServerRepository = $daemonServerRepository;
$this->databaseRepository = $databaseRepository;
$this->databaseManagementService = $databaseManagementService;
$this->repository = $repository;
$this->writer = $writer;
}
@ -97,34 +90,29 @@ class ServerDeletionService
/**
* Delete a server from the panel and remove any associated databases from hosts.
*
* @param int|\Pterodactyl\Models\Server $server
* @param \Pterodactyl\Models\Server $server
*
* @throws \Throwable
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function handle($server)
public function handle(Server $server)
{
try {
$this->daemonServerRepository->setServer($server)->delete();
} catch (RequestException $exception) {
$response = $exception->getResponse();
if (is_null($response) || (! is_null($response) && $response->getStatusCode() !== 404)) {
// If not forcing the deletion, throw an exception, otherwise just log it and
// continue with server deletion process in the panel.
if (! $this->force) {
throw new DaemonConnectionException($exception);
} else {
$this->writer->warning($exception);
}
} catch (DaemonConnectionException $exception) {
if ($this->force) {
$this->writer->warning($exception);
} else {
throw $exception;
}
}
$this->connection->beginTransaction();
$this->databaseRepository->setColumns('id')->findWhere([['server_id', '=', $server->id]])->each(function ($item) {
$this->databaseManagementService->delete($item->id);
});
$this->connection->transaction(function () use ($server) {
$this->databaseRepository->setColumns('id')->findWhere([['server_id', '=', $server->id]])->each(function ($item) {
$this->databaseManagementService->delete($item->id);
});
$this->repository->delete($server->id);
$this->connection->commit();
$this->repository->delete($server->id);
});
}
}

View File

@ -4,24 +4,16 @@ namespace Pterodactyl\Services\Servers;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Traits\Services\HasUserLevels;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
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
{
use HasUserLevels;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
*/
private $daemonServerRepository;
/**
* @var \Illuminate\Database\ConnectionInterface
*/
@ -52,33 +44,38 @@ class StartupModificationService
*/
private $validatorService;
/**
* @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService
*/
private $structureService;
/**
* StartupModificationService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository
* @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $eggRepository
* @param \Pterodactyl\Services\Servers\EnvironmentService $environmentService
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
* @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $structureService
* @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository
* @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService
*/
public function __construct(
ConnectionInterface $connection,
DaemonServerRepositoryInterface $daemonServerRepository,
EggRepositoryInterface $eggRepository,
EnvironmentService $environmentService,
ServerRepositoryInterface $repository,
ServerConfigurationStructureService $structureService,
ServerVariableRepositoryInterface $serverVariableRepository,
VariableValidatorService $validatorService
) {
$this->daemonServerRepository = $daemonServerRepository;
$this->connection = $connection;
$this->eggRepository = $eggRepository;
$this->environmentService = $environmentService;
$this->repository = $repository;
$this->serverVariableRepository = $serverVariableRepository;
$this->validatorService = $validatorService;
$this->structureService = $structureService;
}
/**
@ -89,7 +86,6 @@ class StartupModificationService
* @return \Pterodactyl\Models\Server
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
@ -110,22 +106,8 @@ class StartupModificationService
});
}
$daemonData = [];
if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) {
$this->updateAdministrativeSettings($data, $server, $daemonData);
}
$daemonData = array_merge_recursive($daemonData, [
'build' => [
'env|overwrite' => $this->environmentService->handle($server),
],
]);
try {
$this->daemonServerRepository->setServer($server)->update($daemonData);
} catch (RequestException $exception) {
$this->connection->rollBack();
throw new DaemonConnectionException($exception);
$this->updateAdministrativeSettings($data, $server);
}
$this->connection->commit();
@ -138,12 +120,11 @@ class StartupModificationService
*
* @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)
private function updateAdministrativeSettings(array $data, Server &$server)
{
if (
is_digit(array_get($data, 'egg_id'))
@ -163,13 +144,5 @@ class StartupModificationService
'skip_scripts' => array_get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
'image' => array_get($data, 'docker_image', $server->image),
]);
$daemonData = array_merge($daemonData, [
'build' => ['image' => $server->image],
'service' => array_merge(
$this->repository->getDaemonServiceData($server, true),
['skip_scripts' => $server->skip_scripts]
),
]);
}
}

View File

@ -1,66 +1,57 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Services\Servers;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Server;
use Psr\Log\LoggerInterface as Writer;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
class SuspensionService
{
const ACTION_SUSPEND = 'suspend';
const ACTION_UNSUSPEND = 'unsuspend';
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
*/
protected $daemonServerRepository;
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected $database;
private $connection;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $repository;
private $repository;
/**
* @var \Psr\Log\LoggerInterface
*/
protected $writer;
private $writer;
/**
* @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
*/
private $daemonServerRepository;
/**
* SuspensionService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $database
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
* @param \Psr\Log\LoggerInterface $writer
*/
public function __construct(
ConnectionInterface $database,
DaemonServerRepositoryInterface $daemonServerRepository,
ConnectionInterface $connection,
DaemonServerRepository $daemonServerRepository,
ServerRepositoryInterface $repository,
Writer $writer
LoggerInterface $writer
) {
$this->daemonServerRepository = $daemonServerRepository;
$this->database = $database;
$this->connection = $connection;
$this->repository = $repository;
$this->writer = $writer;
$this->daemonServerRepository = $daemonServerRepository;
}
/**
@ -68,49 +59,26 @@ class SuspensionService
*
* @param int|\Pterodactyl\Models\Server $server
* @param string $action
* @return bool
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function toggle($server, $action = self::ACTION_SUSPEND)
public function toggle(Server $server, $action = self::ACTION_SUSPEND)
{
if (! $server instanceof Server) {
$server = $this->repository->find($server);
}
if (! in_array($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND])) {
throw new InvalidArgumentException(sprintf(
'Action must be either ' . self::ACTION_SUSPEND . ' or ' . self::ACTION_UNSUSPEND . ', %s passed.',
$action
));
}
Assert::oneOf($action, [self::ACTION_SUSPEND, self::ACTION_UNSUSPEND]);
if (
$action === self::ACTION_SUSPEND && $server->suspended ||
$action === self::ACTION_UNSUSPEND && ! $server->suspended
) {
return true;
return;
}
$this->database->beginTransaction();
$this->repository->withoutFreshModel()->update($server->id, [
'suspended' => $action === self::ACTION_SUSPEND,
]);
$this->connection->transaction(function () use ($action, $server) {
$this->repository->withoutFreshModel()->update($server->id, [
'suspended' => $action === self::ACTION_SUSPEND,
]);
try {
$this->daemonServerRepository->setServer($server)->$action();
$this->database->commit();
return true;
} catch (RequestException $exception) {
$response = $exception->getResponse();
$this->writer->warning($exception);
throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [
'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(),
]));
}
$this->daemonServerRepository->setServer($server)->suspend($action === self::ACTION_UNSUSPEND);
});
}
}

View File

@ -1,31 +1,12 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Services\Subusers;
use Pterodactyl\Models\Subuser;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService;
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
class SubuserDeletionService
{
/**
* @var \Illuminate\Database\ConnectionInterface
*/
private $connection;
/**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService
*/
private $keyDeletionService;
/**
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
*/
@ -34,17 +15,11 @@ class SubuserDeletionService
/**
* SubuserDeletionService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyDeletionService $keyDeletionService
* @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository
*/
public function __construct(
ConnectionInterface $connection,
DaemonKeyDeletionService $keyDeletionService,
SubuserRepositoryInterface $repository
) {
$this->connection = $connection;
$this->keyDeletionService = $keyDeletionService;
$this->repository = $repository;
}
@ -52,15 +27,9 @@ class SubuserDeletionService
* Delete a subuser and their associated permissions from the Panel and Daemon.
*
* @param \Pterodactyl\Models\Subuser $subuser
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle(Subuser $subuser)
{
$this->connection->beginTransaction();
$this->keyDeletionService->handle($subuser->server_id, $subuser->user_id);
$this->repository->delete($subuser->id);
$this->connection->commit();
}
}

View File

@ -5,18 +5,12 @@ namespace Pterodactyl\Services\Users;
use Carbon\Carbon;
use Pterodactyl\Models\User;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
class ToggleTwoFactorService
{
/**
* @var \Illuminate\Contracts\Config\Repository
*/
private $config;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
@ -37,16 +31,13 @@ class ToggleTwoFactorService
*
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @param \PragmaRX\Google2FA\Google2FA $google2FA
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
*/
public function __construct(
Encrypter $encrypter,
Google2FA $google2FA,
Repository $config,
UserRepositoryInterface $repository
) {
$this->config = $config;
$this->encrypter = $encrypter;
$this->google2FA = $google2FA;
$this->repository = $repository;
@ -60,19 +51,23 @@ class ToggleTwoFactorService
* @param bool|null $toggleState
* @return bool
*
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid
*/
public function handle(User $user, string $token, bool $toggleState = null): bool
{
$window = $this->config->get('pterodactyl.auth.2fa.window');
$secret = $this->encrypter->decrypt($user->totp_secret);
$isValidToken = $this->google2FA->verifyKey($secret, $token, $window);
$isValidToken = $this->google2FA->verifyKey($secret, $token, config()->get('pterodactyl.auth.2fa.window'));
if (! $isValidToken) {
throw new TwoFactorAuthenticationTokenInvalid;
throw new TwoFactorAuthenticationTokenInvalid(
'The token provided is not valid.'
);
}
$this->repository->withoutFreshModel()->update($user->id, [

View File

@ -3,11 +3,9 @@
namespace Pterodactyl\Services\Users;
use Pterodactyl\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Contracts\Hashing\Hasher;
use Pterodactyl\Traits\Services\HasUserLevels;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService;
use Pterodactyl\Repositories\Eloquent\UserRepository;
class UserUpdateService
{
@ -19,44 +17,33 @@ class UserUpdateService
private $hasher;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
* @var \Pterodactyl\Repositories\Eloquent\UserRepository
*/
private $repository;
/**
* @var \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService
*/
private $revocationService;
/**
* UpdateService constructor.
*
* @param \Illuminate\Contracts\Hashing\Hasher $hasher
* @param \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService $revocationService
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
* @param \Pterodactyl\Repositories\Eloquent\UserRepository $repository
*/
public function __construct(
Hasher $hasher,
RevokeMultipleDaemonKeysService $revocationService,
UserRepositoryInterface $repository
) {
public function __construct(Hasher $hasher, UserRepository $repository)
{
$this->hasher = $hasher;
$this->repository = $repository;
$this->revocationService = $revocationService;
}
/**
* Update the user model instance. If the user has been removed as an administrator
* revoke all of the authentication tokens that have been assigned to their account.
* Update the user model instance.
*
* @param \Pterodactyl\Models\User $user
* @param array $data
* @return \Illuminate\Support\Collection
* @return \Pterodactyl\Models\User
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle(User $user, array $data): Collection
public function handle(User $user, array $data)
{
if (! empty(array_get($data, 'password'))) {
$data['password'] = $this->hasher->make($data['password']);
@ -64,17 +51,9 @@ class UserUpdateService
unset($data['password']);
}
if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) {
if (array_get($data, 'root_admin', 0) == 0 && $user->root_admin) {
$this->revocationService->handle($user, array_get($data, 'ignore_connection_error', false));
}
} else {
unset($data['root_admin']);
}
/** @var \Pterodactyl\Models\User $response */
$response = $this->repository->update($user->id, $data);
return collect([
'model' => $this->repository->update($user->id, $data),
'exceptions' => $this->revocationService->getExceptions(),
]);
return $response;
}
}

View File

@ -33,32 +33,13 @@ trait JavascriptInjection
}
/**
* Injects server javascript into the page to be used by other services.
* Injects the exact array passed in, nothing more.
*
* @param array $args
* @param bool $overwrite
* @return array
*/
public function injectJavascript($args = [], $overwrite = false)
public function plainInject($args = [])
{
$request = $this->request ?? app()->make(Request::class);
$server = $request->attributes->get('server');
$token = $request->attributes->get('server_token');
$response = array_merge_recursive([
'server' => [
'uuid' => $server->uuid,
'uuidShort' => $server->uuidShort,
'daemonSecret' => $token,
],
'server_token' => $token,
'node' => [
'fqdn' => $server->node->fqdn,
'scheme' => $server->node->scheme,
'daemonListen' => $server->node->daemonListen,
],
], $args);
return Javascript::put($overwrite ? $args : $response);
return Javascript::put($args);
}
}

View File

@ -35,6 +35,10 @@ class ServerTransformer extends BaseClientTransformer
'uuid' => $server->uuid,
'name' => $server->name,
'node' => $server->node->name,
'sftp_details' => [
'ip' => $server->node->fqdn,
'port' => $server->node->daemonSFTP,
],
'description' => $server->description,
'allocation' => [
'ip' => $server->allocation->alias,

View File

@ -0,0 +1,55 @@
<?php
namespace Pterodactyl\Transformers\Api\Client;
use Illuminate\Support\Str;
use Pterodactyl\Models\Subuser;
class SubuserTransformer extends BaseClientTransformer
{
protected $availableIncludes = ['permissions'];
/**
* Return the resource name for the JSONAPI output.
*
* @return string
*/
public function getResourceName(): string
{
return Subuser::RESOURCE_NAME;
}
/**
* Transforms a User model into a representation that can be shown to regular
* users of the API.
*
* @param \Pterodactyl\Models\Subuser $model
* @return array
*/
public function transform(Subuser $model)
{
$user = $model->user;
return [
'uuid' => $user->uuid,
'username' => $user->username,
'email' => $user->email,
'image' => 'https://gravatar.com/avatar/' . md5(Str::lower($user->email)),
'2fa_enabled' => $user->use_totp,
'created_at' => $model->created_at->toIso8601String(),
];
}
/**
* Include the permissions associated with this subuser.
*
* @param \Pterodactyl\Models\Subuser $model
* @return \League\Fractal\Resource\Item
*/
public function includePermissions(Subuser $model)
{
return $this->item($model, function (Subuser $model) {
return ['permissions' => $model->permissions->pluck('permission')];
});
}
}

View File

@ -34,12 +34,13 @@
"s1lentium/iptools": "^1.1",
"spatie/laravel-fractal": "^5.6",
"staudenmeir/belongs-to-through": "^2.6",
"symfony/yaml": "^4.0",
"webmozart/assert": "^1.5"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.2",
"barryvdh/laravel-ide-helper": "^2.6",
"codedungeon/phpunit-result-printer": "^0.26",
"codedungeon/phpunit-result-printer": "0.25.1",
"friendsofphp/php-cs-fixer": "^2.15.1",
"laravel/dusk": "^5.5",
"php-mock/php-mock-phpunit": "^2.4",

132
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "54a69da316f2921ebcae63ec6b054468",
"content-hash": "39fbdca3eac026ce6a99684706ffa03b",
"packages": [
{
"name": "appstract/laravel-blade-directives",
@ -4441,6 +4441,65 @@
],
"time": "2019-08-26T08:26:39+00:00"
},
{
"name": "symfony/yaml",
"version": "v4.4.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "76de473358fe802578a415d5bb43c296cf09d211"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/76de473358fe802578a415d5bb43c296cf09d211",
"reference": "76de473358fe802578a415d5bb43c296cf09d211",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<3.4"
},
"require-dev": {
"symfony/console": "^3.4|^4.0|^5.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2019-11-12T14:51:11+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "2.2.1",
@ -4879,16 +4938,16 @@
},
{
"name": "codedungeon/phpunit-result-printer",
"version": "0.26.1",
"version": "0.25.1",
"source": {
"type": "git",
"url": "https://github.com/mikeerickson/phpunit-pretty-result-printer.git",
"reference": "70efe139f174954392582103355a1b4a3d9022e5"
"reference": "4a689ac40366eb4adf166cf4676da7ef30d82315"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mikeerickson/phpunit-pretty-result-printer/zipball/70efe139f174954392582103355a1b4a3d9022e5",
"reference": "70efe139f174954392582103355a1b4a3d9022e5",
"url": "https://api.github.com/repos/mikeerickson/phpunit-pretty-result-printer/zipball/4a689ac40366eb4adf166cf4676da7ef30d82315",
"reference": "4a689ac40366eb4adf166cf4676da7ef30d82315",
"shasum": ""
},
"require": {
@ -4899,7 +4958,7 @@
"symfony/yaml": "^2.7|^3.0|^4.0"
},
"require-dev": {
"phpunit/phpunit": "8.0.*",
"phpunit/phpunit": "7.5.*",
"spatie/phpunit-watcher": "^1.6"
},
"type": "library",
@ -4928,7 +4987,7 @@
"result-printer",
"testing"
],
"time": "2019-02-28T18:52:43+00:00"
"time": "2019-02-01T19:13:43+00:00"
},
{
"name": "composer/ca-bundle",
@ -7546,65 +7605,6 @@
"homepage": "https://symfony.com",
"time": "2019-08-07T11:52:19+00:00"
},
{
"name": "symfony/yaml",
"version": "v4.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686",
"reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<3.4"
},
"require-dev": {
"symfony/console": "~3.4|~4.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.3-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2019-08-20T14:27:59+00:00"
},
{
"name": "theseer/tokenizer",
"version": "1.1.3",

View File

@ -1,56 +0,0 @@
<?php
return [
/*
* The destination path for the javascript file.
*/
'path' => 'public/js',
/*
* The destination filename for the javascript file.
*/
'filename' => 'laroute',
/*
* The namespace for the helper functions. By default this will bind them to
* `window.laroute`.
*/
'namespace' => 'Router',
/*
* Generate absolute URLs
*
* Set the Application URL in config/app.php
*/
'absolute' => false,
/*
* The Filter Method
*
* 'all' => All routes except "'laroute' => false"
* 'only' => Only "'laroute' => true" routes
* 'force' => All routes, ignored "laroute" route parameter
*/
'filter' => 'all',
/*
* Controller Namespace
*
* Set here your controller namespace (see RouteServiceProvider -> $namespace) for cleaner action calls
* e.g. 'App\Http\Controllers'
*/
'action_namespace' => '',
/*
* The path to the template `laroute.js` file. This is the file that contains
* the ported helper Laravel url/route functions and the route data to go
* with them.
*/
'template' => 'vendor/lord/laroute/src/templates/laroute.js',
/*
* Appends a prefix to URLs. By default the prefix is an empty string.
*
*/
'prefix' => '',
];

View File

@ -4,24 +4,35 @@
"@fortawesome/fontawesome-svg-core": "^1.2.19",
"@fortawesome/free-solid-svg-icons": "^5.9.0",
"@fortawesome/react-fontawesome": "^0.1.4",
"@hot-loader/react-dom": "^16.8.6",
"axios": "^0.18.0",
"@types/react-google-recaptcha": "^1.1.1",
"axios": "^0.19.0",
"ayu-ace": "^2.0.4",
"brace": "^0.11.1",
"chart.js": "^2.8.0",
"classnames": "^2.2.6",
"date-fns": "^1.29.0",
"easy-peasy": "^3.0.2",
"easy-peasy": "^3.2.3",
"events": "^3.0.0",
"formik": "^1.5.7",
"i18next": "^19.0.0",
"i18next-chained-backend": "^2.0.0",
"i18next-localstorage-backend": "^3.0.0",
"i18next-xhr-backend": "^3.2.2",
"jquery": "^3.3.1",
"lodash-es": "^4.17.15",
"path": "^0.12.7",
"query-string": "^6.7.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-hot-loader": "^4.12.13",
"react": "^16.12.0",
"react-dom": "npm:@hot-loader/react-dom",
"react-google-recaptcha": "^2.0.1",
"react-hot-loader": "^4.12.18",
"react-i18next": "^11.2.1",
"react-redux": "^7.1.0",
"react-router-dom": "^5.0.1",
"react-transition-group": "^4.1.0",
"react-router-dom": "^5.1.2",
"react-transition-group": "^4.3.0",
"sockette": "^2.0.6",
"styled-components": "^4.3.2",
"styled-components": "^4.4.1",
"styled-components-breakpoint": "^3.0.0-preview.20",
"use-react-router": "^1.0.7",
"uuid": "^3.3.2",
"xterm": "^3.14.4",
@ -30,27 +41,30 @@
"yup": "^0.27.0"
},
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.6.0",
"@babel/runtime": "^7.6.0",
"@babel/core": "^7.7.5",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.7.5",
"@babel/preset-env": "^7.7.5",
"@babel/preset-react": "^7.7.4",
"@babel/preset-typescript": "^7.7.4",
"@babel/runtime": "^7.7.5",
"@types/chart.js": "^2.8.5",
"@types/classnames": "^2.2.8",
"@types/events": "^3.0.0",
"@types/feather-icons": "^4.7.0",
"@types/lodash": "^4.14.119",
"@types/lodash-es": "^4.17.3",
"@types/node": "^12.6.9",
"@types/query-string": "^6.3.0",
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.4",
"@types/react": "^16.9.15",
"@types/react-dom": "^16.9.4",
"@types/react-redux": "^7.1.1",
"@types/react-router-dom": "^4.3.3",
"@types/react-router": "^5.1.3",
"@types/react-router-dom": "^5.1.3",
"@types/react-transition-group": "^2.9.2",
"@types/styled-components": "^4.1.18",
"@types/styled-components": "^4.4.0",
"@types/uuid": "^3.4.5",
"@types/webpack-env": "^1.13.6",
"@types/yup": "^0.26.17",
@ -59,24 +73,26 @@
"babel-loader": "^8.0.6",
"babel-plugin-styled-components": "^1.10.6",
"babel-plugin-tailwind-components": "^0.5.10",
"css-loader": "^3.2.0",
"css-loader": "^3.2.1",
"cssnano": "^4.1.10",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-react-hooks": "^2.1.2",
"eslint-plugin-standard": "^4.0.0",
"fork-ts-checker-webpack-plugin": "^1.5.0",
"glob-all": "^3.1.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.8.0",
"postcss": "^6.0.21",
"postcss-import": "^11.1.0",
"postcss": "^7.0.24",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^3.4.0",
"precss": "^3.1.2",
"purgecss-webpack-plugin": "^1.1.0",
"postcss-preset-env": "^6.7.0",
"precss": "^4.0.0",
"purgecss-webpack-plugin": "^1.6.0",
"redux-devtools-extension": "^2.13.8",
"resolve-url-loader": "^3.0.0",
"source-map-loader": "^0.2.4",
"style-loader": "^0.23.1",
@ -84,20 +100,24 @@
"terser-webpack-plugin": "^1.3.0",
"ts-loader": "^5.3.3",
"typescript": "^3.6.3",
"webpack": "^4.40.2",
"webpack": "^4.41.2",
"webpack-assets-manifest": "^3.1.1",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.1",
"webpack-manifest-plugin": "^2.0.3"
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0",
"webpack-manifest-plugin": "^2.0.3",
"yarn-deduplicate": "^1.1.1"
},
"scripts": {
"clean": "rm -rf public/assets/*.js && rm -rf public/assets/*.css",
"clean": "rm -rf public/assets/*.{js,css,map}",
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --watch --progress",
"build": "NODE_ENV=development ./node_modules/.bin/webpack --progress",
"build:production": "NODE_ENV=production ./node_modules/.bin/webpack",
"build:production": "yarn run clean && NODE_ENV=production ./node_modules/.bin/webpack --mode production",
"serve": "yarn run clean && PUBLIC_PATH=https://pterodactyl.test:8080 NODE_ENV=development webpack-dev-server --host 0.0.0.0 --hot --https --key /etc/ssl/private/pterodactyl.test-key.pem --cert /etc/ssl/private/pterodactyl.test.pem"
},
"browserslist": [
"defaults"
"> 0.5%",
"last 2 versions",
"firefox esr",
"not dead"
]
}

2
public/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
assets/*
!assets/*.svg

Some files were not shown because too many files have changed in this diff Show More