Merge branch 'develop' into feature/server-mounts

This commit is contained in:
Matthew Penner 2020-07-11 12:29:30 -06:00 committed by GitHub
commit 295f09ca43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
195 changed files with 5395 additions and 5417 deletions

View file

@ -3,11 +3,10 @@
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
use Pterodactyl\Models\Node;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Models\Allocation;
use Pterodactyl\Services\Allocations\AssignmentService;
use Pterodactyl\Services\Allocations\AllocationDeletionService;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Transformers\Api\Application\AllocationTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest;
@ -26,41 +25,32 @@ class AllocationController extends ApplicationApiController
*/
private $deletionService;
/**
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
*/
private $repository;
/**
* AllocationController constructor.
*
* @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService
* @param \Pterodactyl\Services\Allocations\AllocationDeletionService $deletionService
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $repository
*/
public function __construct(
AssignmentService $assignmentService,
AllocationDeletionService $deletionService,
AllocationRepositoryInterface $repository
AllocationDeletionService $deletionService
) {
parent::__construct();
$this->assignmentService = $assignmentService;
$this->deletionService = $deletionService;
$this->repository = $repository;
}
/**
* Return all of the allocations that exist for a given node.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest $request
* @param \Pterodactyl\Models\Node $node
* @return array
*/
public function index(GetAllocationsRequest $request): array
public function index(GetAllocationsRequest $request, Node $node): array
{
$allocations = $this->repository->getPaginatedAllocationsForNode(
$request->getModel(Node::class)->id, 50
);
$allocations = $node->allocations()->paginate(50);
return $this->fractal->collection($allocations)
->transformWith($this->getTransformer(AllocationTransformer::class))
@ -71,32 +61,35 @@ class AllocationController extends ApplicationApiController
* Store new allocations for a given node.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Allocations\StoreAllocationRequest $request
* @return \Illuminate\Http\Response
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
* @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
*/
public function store(StoreAllocationRequest $request): Response
public function store(StoreAllocationRequest $request, Node $node): JsonResponse
{
$this->assignmentService->handle($request->getModel(Node::class), $request->validated());
$this->assignmentService->handle($node, $request->validated());
return response('', 204);
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
}
/**
* Delete a specific allocation from the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Allocations\DeleteAllocationRequest $request
* @return \Illuminate\Http\Response
* @param \Pterodactyl\Models\Node $node
* @param \Pterodactyl\Models\Allocation $allocation
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
*/
public function delete(DeleteAllocationRequest $request): Response
public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): JsonResponse
{
$this->deletionService->handle($request->getModel(Allocation::class));
$this->deletionService->handle($allocation);
return response('', 204);
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
}
}

View file

@ -10,6 +10,40 @@ use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
abstract class ClientApiController extends ApplicationApiController
{
/**
* Returns only the includes which are valid for the given transformer.
*
* @param \Pterodactyl\Transformers\Api\Client\BaseClientTransformer $transformer
* @param array $merge
* @return string[]
*/
protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = [])
{
$filtered = array_filter($this->parseIncludes(), function ($datum) use ($transformer) {
return in_array($datum, $transformer->getAvailableIncludes());
});
return array_merge($filtered, $merge);
}
/**
* Returns the parsed includes for this request.
*
* @return string[]
*/
protected function parseIncludes()
{
$includes = $this->request->query('include') ?? [];
if (! is_string($includes)) {
return $includes;
}
return array_map(function ($item) {
return trim($item);
}, explode(',', $includes));
}
/**
* Return an instance of an application transformer.
*

View file

@ -3,7 +3,9 @@
namespace Pterodactyl\Http\Controllers\Api\Client;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Permission;
use Spatie\QueryBuilder\QueryBuilder;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Transformers\Api\Client\ServerTransformer;
use Pterodactyl\Http\Requests\Api\Client\GetServersRequest;
@ -36,32 +38,36 @@ class ClientController extends ClientApiController
*/
public function index(GetServersRequest $request): array
{
// Check for the filter parameter on the request.
switch ($request->input('filter')) {
case 'all':
$filter = User::FILTER_LEVEL_ALL;
break;
case 'admin':
$filter = User::FILTER_LEVEL_ADMIN;
break;
case 'owner':
$filter = User::FILTER_LEVEL_OWNER;
break;
case 'subuser-of':
default:
$filter = User::FILTER_LEVEL_SUBUSER;
break;
$user = $request->user();
$level = $request->getFilterLevel();
$transformer = $this->getTransformer(ServerTransformer::class);
// Start the query builder and ensure we eager load any requested relationships from the request.
$builder = Server::query()->with($this->getIncludesForTransformer($transformer, ['node']));
if ($level === User::FILTER_LEVEL_OWNER) {
$builder = $builder->where('owner_id', $request->user()->id);
}
// If set to all, display all servers they can access, including those they access as an
// admin. If set to subuser, only return the servers they can access because they are owner,
// or marked as a subuser of the server.
elseif (($level === User::FILTER_LEVEL_ALL && ! $user->root_admin) || $level === User::FILTER_LEVEL_SUBUSER) {
$builder = $builder->whereIn('id', $user->accessibleServers()->pluck('id')->all());
}
// If set to admin, only display the servers a user can access because they are an administrator.
// This means only servers the user would not have access to if they were not an admin (because they
// are not an owner or subuser) are returned.
elseif ($level === User::FILTER_LEVEL_ADMIN && $user->root_admin) {
$builder = $builder->whereNotIn('id', $user->accessibleServers()->pluck('id')->all());
}
$servers = $this->repository
->setSearchTerm($request->input('query'))
->filterUserAccessServers(
$request->user(), $filter, config('pterodactyl.paginate.frontend.servers')
);
$builder = QueryBuilder::for($builder)->allowedFilters(
'uuid', 'name', 'external_id'
);
return $this->fractal->collection($servers)
->transformWith($this->getTransformer(ServerTransformer::class))
->toArray();
$servers = $builder->paginate(min($request->query('per_page', 50), 100))->appends($request->query());
return $this->fractal->transformWith($transformer)->collection($servers)->toArray();
}
/**

View file

@ -159,7 +159,7 @@ class FileController extends ClientApiController
{
$this->fileRepository
->setServer($server)
->createDirectory($request->input('name'), $request->input('directory', '/'));
->createDirectory($request->input('name'), $request->input('root', '/'));
return Response::create('', Response::HTTP_NO_CONTENT);
}

View file

@ -0,0 +1,127 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Pterodactyl\Models\Server;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Models\Allocation;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Repositories\Eloquent\AllocationRepository;
use Pterodactyl\Transformers\Api\Client\AllocationTransformer;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\DeleteAllocationRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\UpdateAllocationRequest;
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\SetPrimaryAllocationRequest;
class NetworkAllocationController extends ClientApiController
{
/**
* @var \Pterodactyl\Repositories\Eloquent\AllocationRepository
*/
private $repository;
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
private $serverRepository;
/**
* NetworkController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $repository
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $serverRepository
*/
public function __construct(
AllocationRepository $repository,
ServerRepository $serverRepository
) {
parent::__construct();
$this->repository = $repository;
$this->serverRepository = $serverRepository;
}
/**
* Lists all of the allocations available to a server and wether or
* not they are currently assigned as the primary for this server.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest $request
* @param \Pterodactyl\Models\Server $server
* @return array
*/
public function index(GetNetworkRequest $request, Server $server): array
{
return $this->fractal->collection($server->allocations)
->transformWith($this->getTransformer(AllocationTransformer::class))
->toArray();
}
/**
* Set the primary allocation for a server.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\UpdateAllocationRequest $request
* @param \Pterodactyl\Models\Server $server
* @param \Pterodactyl\Models\Allocation $allocation
* @return array
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(UpdateAllocationRequest $request, Server $server, Allocation $allocation): array
{
$allocation = $this->repository->update($allocation->id, [
'notes' => $request->input('notes'),
]);
return $this->fractal->item($allocation)
->transformWith($this->getTransformer(AllocationTransformer::class))
->toArray();
}
/**
* Set the primary allocation for a server.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\SetPrimaryAllocationRequest $request
* @param \Pterodactyl\Models\Server $server
* @param \Pterodactyl\Models\Allocation $allocation
* @return array
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function setPrimary(SetPrimaryAllocationRequest $request, Server $server, Allocation $allocation): array
{
$this->serverRepository->update($server->id, ['allocation_id' => $allocation->id]);
return $this->fractal->item($allocation)
->transformWith($this->getTransformer(AllocationTransformer::class))
->toArray();
}
/**
* Delete an allocation from a server.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\DeleteAllocationRequest $request
* @param \Pterodactyl\Models\Server $server
* @param \Pterodactyl\Models\Allocation $allocation
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function delete(DeleteAllocationRequest $request, Server $server, Allocation $allocation)
{
if ($allocation->id === $server->allocation_id) {
throw new DisplayException(
'Cannot delete the primary allocation for a server.'
);
}
$this->repository->update($allocation->id, ['server_id' => null, 'notes' => null]);
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
}
}

View file

@ -1,48 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Pterodactyl\Models\Server;
use Pterodactyl\Repositories\Eloquent\AllocationRepository;
use Pterodactyl\Transformers\Api\Client\AllocationTransformer;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest;
class NetworkController extends ClientApiController
{
/**
* @var \Pterodactyl\Repositories\Eloquent\AllocationRepository
*/
private $repository;
/**
* NetworkController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $repository
*/
public function __construct(AllocationRepository $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Lists all of the allocations available to a server and wether or
* not they are currently assigned as the primary for this server.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest $request
* @param \Pterodactyl\Models\Server $server
* @return array
*/
public function index(GetNetworkRequest $request, Server $server): array
{
$allocations = $this->repository->findWhere([
['server_id', '=', $server->id],
]);
return $this->fractal->collection($allocations)
->transformWith($this->getTransformer(AllocationTransformer::class))
->toArray();
}
}

View file

@ -2,8 +2,6 @@
namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
@ -27,15 +25,10 @@ class IndexController extends Controller
/**
* Returns listing of user's servers.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
public function index()
{
$servers = $this->repository->setSearchTerm($request->input('query'))->filterUserAccessServers(
$request->user(), User::FILTER_LEVEL_ALL, config('pterodactyl.paginate.frontend.servers')
);
return view('templates/base.core', ['servers' => $servers]);
return view('templates/base.core');
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Pterodactyl\Http\Middleware\Api\Client\Server;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class AllocationBelongsToServer
{
/**
* Ensure that the allocation found in the URL belongs to the server being queried.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function handle(Request $request, Closure $next)
{
/** @var \Pterodactyl\Models\Server $server */
$server = $request->route()->parameter('server');
/** @var \Pterodactyl\Models\Allocation|null $allocation */
$allocation = $request->route()->parameter('allocation');
if ($allocation && $allocation->server_id !== $server->id) {
throw new NotFoundHttpException;
}
return $next($request);
}
}

View file

@ -65,7 +65,7 @@ class AuthenticateServerAccess
}
if ($server->suspended) {
throw new AccessDeniedHttpException('This server is currenty suspended and the functionality requested is unavailable.');
throw new AccessDeniedHttpException('This server is currently suspended and the functionality requested is unavailable.');
}
if (! $server->isInstalled()) {

View file

@ -4,12 +4,12 @@ namespace Pterodactyl\Http\Middleware\Api\Client;
use Closure;
use Pterodactyl\Models\Backup;
use Pterodactyl\Models\Database;
use Illuminate\Container\Container;
use Pterodactyl\Contracts\Extensions\HashidsInterface;
use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
class SubstituteClientApiBindings extends ApiSubstituteBindings
{
@ -43,17 +43,9 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings
});
$this->router->bind('database', function ($value) use ($request) {
try {
$id = Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value);
$id = Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value);
return Container::getInstance()->make(DatabaseRepositoryInterface::class)->findFirstWhere([
['id', '=', $id],
]);
} catch (RecordNotFoundException $exception) {
$request->attributes->set('is_missing_model', true);
return null;
}
return Database::query()->where('id', $id)->firstOrFail();
});
$this->router->model('backup', Backup::class, function ($value) {

View file

@ -2,6 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Client;
use Pterodactyl\Models\User;
class GetServersRequest extends ClientApiRequest
{
/**
@ -11,4 +13,28 @@ class GetServersRequest extends ClientApiRequest
{
return true;
}
/**
* Return the filtering method for servers when the client base endpoint is requested.
*
* @return int
*/
public function getFilterLevel(): int
{
switch ($this->input('type')) {
case 'all':
return User::FILTER_LEVEL_ALL;
break;
case 'admin':
return User::FILTER_LEVEL_ADMIN;
break;
case 'owner':
return User::FILTER_LEVEL_OWNER;
break;
case 'subuser-of':
default:
return User::FILTER_LEVEL_SUBUSER;
break;
}
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Network;
use Pterodactyl\Models\Permission;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class DeleteAllocationRequest extends ClientApiRequest
{
/**
* @return string
*/
public function permission(): string
{
return Permission::ACTION_ALLOCATION_DELETE;
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Network;
class SetPrimaryAllocationRequest extends UpdateAllocationRequest
{
/**
* @return array
*/
public function rules(): array
{
return [];
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Network;
use Pterodactyl\Models\Allocation;
use Pterodactyl\Models\Permission;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
class UpdateAllocationRequest extends ClientApiRequest
{
/**
* @return string
*/
public function permission(): string
{
return Permission::ACTION_ALLOCATION_UPDATE;
}
/**
* @return array
*/
public function rules(): array
{
$rules = Allocation::getRules();
return [
'notes' => array_merge($rules['notes'], ['present']),
];
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace Pterodactyl\Http\ViewComposers\Server;
use Illuminate\View\View;
use Illuminate\Http\Request;
class ServerDataComposer
{
/**
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* ServerDataComposer constructor.
*
* @param \Illuminate\Http\Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Attach server data to a view automatically.
*
* @param \Illuminate\View\View $view
*/
public function compose(View $view)
{
$server = $this->request->get('server');
$view->with('server', $server);
$view->with('node', object_get($server, 'node'));
$view->with('daemon_token', $this->request->get('server_token'));
}
}

View file

@ -1,51 +0,0 @@
<?php
namespace Pterodactyl\Http\ViewComposers;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class ServerListComposer
{
/**
* @var \Illuminate\Http\Request
*/
private $request;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
private $repository;
/**
* ServerListComposer constructor.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(Request $request, ServerRepositoryInterface $repository)
{
$this->request = $request;
$this->repository = $repository;
}
/**
* Attach a list of servers the user can access to the view.
*
* @param \Illuminate\View\View $view
*/
public function compose(View $view)
{
if (! $this->request->user()) {
return;
}
$servers = $this->repository
->setColumns(['id', 'owner_id', 'uuidShort', 'name', 'description'])
->filterUserAccessServers($this->request->user(), User::FILTER_LEVEL_SUBUSER, false);
$view->with('sidebarServerList', $servers);
}
}