Merge branch 'feature/api-v1' into develop

This commit is contained in:
Dane Everitt 2018-01-30 20:38:19 -06:00
commit 6ef62c7f74
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
207 changed files with 7084 additions and 3231 deletions

View file

@ -0,0 +1,58 @@
<?php
namespace Pterodactyl\Console\Commands\Migration;
use Pterodactyl\Models\ApiKey;
use Illuminate\Console\Command;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
class CleanOrphanedApiKeysCommand extends Command
{
/**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
*/
private $repository;
/**
* @var string
*/
protected $signature = 'p:migration:clean-orphaned-keys';
/**
* @var string
*/
protected $description = 'Cleans API keys from the database that are not assigned a specific role.';
/**
* CleanOrphanedApiKeysCommand constructor.
*
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
*/
public function __construct(ApiKeyRepositoryInterface $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Delete all orphaned API keys from the database when upgrading from 0.6 to 0.7.
*
* @return null|void
*/
public function handle()
{
$count = $this->repository->findCountWhere([['key_type', '=', ApiKey::TYPE_NONE]]);
$continue = $this->confirm(
'This action will remove ' . $count . ' keys from the database. Are you sure you wish to continue?', false
);
if (! $continue) {
return null;
}
$this->info('Deleting keys...');
$this->repository->deleteWhere([['key_type', '=', ApiKey::TYPE_NONE]]);
$this->info('Keys were successfully deleted.');
}
}

View file

@ -3,6 +3,7 @@
namespace Pterodactyl\Contracts\Repository; namespace Pterodactyl\Contracts\Repository;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
interface AllocationRepositoryInterface extends RepositoryInterface interface AllocationRepositoryInterface extends RepositoryInterface
{ {
@ -23,6 +24,15 @@ interface AllocationRepositoryInterface extends RepositoryInterface
*/ */
public function getAllocationsForNode(int $node): Collection; public function getAllocationsForNode(int $node): Collection;
/**
* Return all of the allocations for a node in a paginated format.
*
* @param int $node
* @param int $perPage
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function getPaginatedAllocationsForNode(int $node, int $perPage = 100): LengthAwarePaginator;
/** /**
* Return all of the unique IPs that exist for a given node. * Return all of the unique IPs that exist for a given node.
* *
@ -30,4 +40,44 @@ interface AllocationRepositoryInterface extends RepositoryInterface
* @return \Illuminate\Support\Collection * @return \Illuminate\Support\Collection
*/ */
public function getUniqueAllocationIpsForNode(int $node): Collection; public function getUniqueAllocationIpsForNode(int $node): Collection;
/**
* Return all of the allocations that exist for a node that are not currently
* allocated.
*
* @param int $node
* @return array
*/
public function getUnassignedAllocationIds(int $node): array;
/**
* Get an array of all allocations that are currently assigned to a given server.
*
* @param int $server
* @return array
*/
public function getAssignedAllocationIds(int $server): array;
/**
* Return a concated result set of node ips that already have at least one
* server assigned to that IP. This allows for filtering out sets for
* dedicated allocation IPs.
*
* If an array of nodes is passed the results will be limited to allocations
* in those nodes.
*
* @param array $nodes
* @return array
*/
public function getDiscardableDedicatedAllocations(array $nodes = []): array;
/**
* Return a single allocation from those meeting the requirements.
*
* @param array $nodes
* @param array $ports
* @param bool $dedicated
* @return \Pterodactyl\Models\Allocation|null
*/
public function getRandomAllocation(array $nodes, array $ports, bool $dedicated = false);
} }

View file

@ -1,24 +1,43 @@
<?php <?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Contracts\Repository; namespace Pterodactyl\Contracts\Repository;
use Pterodactyl\Models\APIKey; use Pterodactyl\Models\User;
use Illuminate\Support\Collection;
interface ApiKeyRepositoryInterface extends RepositoryInterface interface ApiKeyRepositoryInterface extends RepositoryInterface
{ {
/** /**
* Load permissions for a key onto the model. * Get all of the account API keys that exist for a specific user.
* *
* @param \Pterodactyl\Models\APIKey $model * @param \Pterodactyl\Models\User $user
* @param bool $refresh * @return \Illuminate\Support\Collection
* @return \Pterodactyl\Models\APIKey
*/ */
public function loadPermissions(APIKey $model, bool $refresh = false): APIKey; public function getAccountKeys(User $user): Collection;
/**
* Get all of the application API keys that exist for a specific user.
*
* @param \Pterodactyl\Models\User $user
* @return \Illuminate\Support\Collection
*/
public function getApplicationKeys(User $user): Collection;
/**
* Delete an account API key from the panel for a specific user.
*
* @param \Pterodactyl\Models\User $user
* @param string $identifier
* @return int
*/
public function deleteAccountKey(User $user, string $identifier): int;
/**
* Delete an application API key from the panel for a specific user.
*
* @param \Pterodactyl\Models\User $user
* @param string $identifier
* @return int
*/
public function deleteApplicationKey(User $user, string $identifier): int;
} }

View file

@ -2,6 +2,7 @@
namespace Pterodactyl\Contracts\Repository; namespace Pterodactyl\Contracts\Repository;
use Generator;
use Pterodactyl\Models\Node; use Pterodactyl\Models\Node;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
@ -62,4 +63,15 @@ interface NodeRepositoryInterface extends RepositoryInterface, SearchableInterfa
* @return \Illuminate\Support\Collection * @return \Illuminate\Support\Collection
*/ */
public function getNodesForServerCreation(): Collection; public function getNodesForServerCreation(): Collection;
/**
* Return the IDs of all nodes that exist in the provided locations and have the space
* available to support the additional disk and memory provided.
*
* @param array $locations
* @param int $disk
* @param int $memory
* @return \Generator
*/
public function getNodesWithResourceUse(array $locations, int $disk, int $memory): Generator;
} }

View file

@ -3,6 +3,7 @@
namespace Pterodactyl\Contracts\Repository; namespace Pterodactyl\Contracts\Repository;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
interface RepositoryInterface interface RepositoryInterface
{ {
@ -175,6 +176,14 @@ interface RepositoryInterface
*/ */
public function all(): Collection; public function all(): Collection;
/**
* Return a paginated result set using a search term if set on the repository.
*
* @param int $perPage
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function paginated(int $perPage): LengthAwarePaginator;
/** /**
* Insert a single or multiple records into the database at once skipping * Insert a single or multiple records into the database at once skipping
* validation and mass assignment checking. * validation and mass assignment checking.

View file

@ -1,19 +1,16 @@
<?php <?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; namespace Pterodactyl\Exceptions;
use Log; use Log;
use Throwable; use Throwable;
use Illuminate\Http\Response;
use Prologue\Alerts\AlertsMessageBag;
class DisplayException extends PterodactylException class DisplayException extends PterodactylException
{ {
const LEVEL_DEBUG = 'debug';
const LEVEL_INFO = 'info';
const LEVEL_WARNING = 'warning'; const LEVEL_WARNING = 'warning';
const LEVEL_ERROR = 'error'; const LEVEL_ERROR = 'error';
@ -32,13 +29,13 @@ class DisplayException extends PterodactylException
*/ */
public function __construct($message, Throwable $previous = null, $level = self::LEVEL_ERROR, $code = 0) public function __construct($message, Throwable $previous = null, $level = self::LEVEL_ERROR, $code = 0)
{ {
$this->level = $level; parent::__construct($message, $code, $previous);
if (! is_null($previous)) { if (! is_null($previous)) {
Log::{$level}($previous); Log::{$level}($previous);
} }
parent::__construct($message, $code, $previous); $this->level = $level;
} }
/** /**
@ -48,4 +45,33 @@ class DisplayException extends PterodactylException
{ {
return $this->level; return $this->level;
} }
/**
* @return int
*/
public function getStatusCode()
{
return Response::HTTP_BAD_REQUEST;
}
/**
* Render the exception to the user by adding a flashed message to the session
* and then redirecting them back to the page that they came from. If the
* request originated from an API hit, return the error in JSONAPI spec format.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
*/
public function render($request)
{
if ($request->expectsJson()) {
return response()->json(Handler::convertToArray($this, [
'detail' => $this->getMessage(),
]), method_exists($this, 'getStatusCode') ? $this->getStatusCode() : Response::HTTP_BAD_REQUEST);
}
app()->make(AlertsMessageBag::class)->danger($this->getMessage())->flash();
return redirect()->back()->withInput();
}
} }

View file

@ -1,14 +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\Exceptions;
class DisplayValidationException extends DisplayException
{
}

View file

@ -3,13 +3,11 @@
namespace Pterodactyl\Exceptions; namespace Pterodactyl\Exceptions;
use Exception; use Exception;
use Prologue\Alerts\Facades\Alert;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
use Illuminate\Session\TokenMismatchException; use Illuminate\Session\TokenMismatchException;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Pterodactyl\Exceptions\Model\DataValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
@ -25,8 +23,6 @@ class Handler extends ExceptionHandler
AuthenticationException::class, AuthenticationException::class,
AuthorizationException::class, AuthorizationException::class,
DisplayException::class, DisplayException::class,
DataValidationException::class,
DisplayValidationException::class,
HttpException::class, HttpException::class,
ModelNotFoundException::class, ModelNotFoundException::class,
RecordNotFoundException::class, RecordNotFoundException::class,
@ -34,6 +30,18 @@ class Handler extends ExceptionHandler
ValidationException::class, ValidationException::class,
]; ];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'token',
'secret',
'password',
'password_confirmation',
];
/** /**
* Report or log an exception. * Report or log an exception.
* *
@ -53,40 +61,78 @@ class Handler extends ExceptionHandler
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Exception $exception * @param \Exception $exception
* @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
* *
* @throws \Exception * @throws \Exception
*/ */
public function render($request, Exception $exception) public function render($request, Exception $exception)
{ {
if ($request->expectsJson() || $request->isJson() || $request->is(...config('pterodactyl.json_routes'))) { return parent::render($request, $exception);
$exception = $this->prepareException($exception); }
if (config('app.debug') || $this->isHttpException($exception) || $exception instanceof DisplayException) { /**
$displayError = $exception->getMessage(); * @param \Illuminate\Http\Request $request
} else { * @param \Illuminate\Validation\ValidationException $exception
$displayError = 'An unhandled exception was encountered with this request.'; * @return \Illuminate\Http\JsonResponse
*/
public function invalidJson($request, ValidationException $exception)
{
$codes = collect($exception->validator->failed())->mapWithKeys(function ($reasons, $field) {
$cleaned = [];
foreach ($reasons as $reason => $attrs) {
$cleaned[] = snake_case($reason);
} }
$response = response()->json( return [str_replace('.', '_', $field) => $cleaned];
[ })->toArray();
'error' => $displayError,
'http_code' => (method_exists($exception, 'getStatusCode')) ? $exception->getStatusCode() : 500, $errors = collect($exception->errors())->map(function ($errors, $field) use ($codes) {
'trace' => (! config('app.debug')) ? null : $exception->getTrace(), $response = [];
foreach ($errors as $key => $error) {
$response[] = [
'code' => array_get($codes, str_replace('.', '_', $field) . '.' . $key),
'detail' => $error,
'source' => ['field' => $field],
];
}
return $response;
})->flatMap(function ($errors) {
return $errors;
})->toArray();
return response()->json(['errors' => $errors], $exception->status);
}
/**
* Return the exception as a JSONAPI representation for use on API requests.
*
* @param \Exception $exception
* @param array $override
* @return array
*/
public static function convertToArray(Exception $exception, array $override = []): array
{
$error = [
'code' => class_basename($exception),
'status' => method_exists($exception, 'getStatusCode') ? strval($exception->getStatusCode()) : '500',
'detail' => 'An error was encountered while processing this request.',
];
if (config('app.debug')) {
$error = array_merge($error, [
'detail' => $exception->getMessage(),
'source' => [
'line' => $exception->getLine(),
'file' => str_replace(base_path(), '', $exception->getFile()),
], ],
$this->isHttpException($exception) ? $exception->getStatusCode() : 500, 'meta' => [
$this->isHttpException($exception) ? $exception->getHeaders() : [], 'trace' => explode("\n", $exception->getTraceAsString()),
JSON_UNESCAPED_SLASHES ],
); ]);
parent::report($exception);
} elseif ($exception instanceof DisplayException) {
Alert::danger($exception->getMessage())->flash();
return redirect()->back()->withInput();
} }
return (isset($response)) ? $response : parent::render($request, $exception); return ['errors' => [array_merge($error, $override)]];
} }
/** /**
@ -104,4 +150,16 @@ class Handler extends ExceptionHandler
return redirect()->guest(route('auth.login')); return redirect()->guest(route('auth.login'));
} }
/**
* Converts an exception into an array to render in the response. Overrides
* Laravel's built-in converter to output as a JSONAPI spec compliant object.
*
* @param \Exception $exception
* @return array
*/
protected function convertExceptionToArray(Exception $exception)
{
return self::convertToArray($exception);
}
} }

View file

@ -2,6 +2,7 @@
namespace Pterodactyl\Exceptions\Http\Connection; namespace Pterodactyl\Exceptions\Http\Connection;
use Illuminate\Http\Response;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
@ -10,7 +11,7 @@ class DaemonConnectionException extends DisplayException
/** /**
* @var int * @var int
*/ */
private $statusCode = 500; private $statusCode = Response::HTTP_GATEWAY_TIMEOUT;
/** /**
* Throw a displayable exception caused by a daemon connection error. * Throw a displayable exception caused by a daemon connection error.

View file

@ -1,20 +1,21 @@
<?php <?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\Model; namespace Pterodactyl\Exceptions\Model;
use Illuminate\Contracts\Validation\Validator; use Illuminate\Contracts\Validation\Validator;
use Illuminate\Validation\ValidationException; use Pterodactyl\Exceptions\PterodactylException;
use Illuminate\Contracts\Support\MessageProvider; use Illuminate\Contracts\Support\MessageProvider;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class DataValidationException extends ValidationException implements MessageProvider class DataValidationException extends PterodactylException implements HttpExceptionInterface, MessageProvider
{ {
/**
* The validator instance.
*
* @var \Illuminate\Contracts\Validation\Validator
*/
public $validator;
/** /**
* DataValidationException constructor. * DataValidationException constructor.
* *
@ -22,14 +23,38 @@ class DataValidationException extends ValidationException implements MessageProv
*/ */
public function __construct(Validator $validator) public function __construct(Validator $validator)
{ {
parent::__construct($validator); parent::__construct(
'Data integrity exception encountered while performing database write operation. ' . $validator->errors()->toJson()
);
$this->validator = $validator;
} }
/** /**
* Return the validator message bag.
*
* @return \Illuminate\Support\MessageBag * @return \Illuminate\Support\MessageBag
*/ */
public function getMessageBag() public function getMessageBag()
{ {
return $this->validator->errors(); return $this->validator->errors();
} }
/**
* Return the status code for this request.
*
* @return int
*/
public function getStatusCode()
{
return 500;
}
/**
* @return array
*/
public function getHeaders()
{
return [];
}
} }

View file

@ -1,11 +1,4 @@
<?php <?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; namespace Pterodactyl\Exceptions;

View file

@ -1,14 +1,9 @@
<?php <?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\Repository\Daemon; namespace Pterodactyl\Exceptions\Repository\Daemon;
class InvalidPowerSignalException extends \Exception use Pterodactyl\Exceptions\Repository\RepositoryException;
class InvalidPowerSignalException extends RepositoryException
{ {
} }

View file

@ -1,11 +1,4 @@
<?php <?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\Repository; namespace Pterodactyl\Exceptions\Repository;

View file

@ -1,21 +1,20 @@
<?php <?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\Repository; namespace Pterodactyl\Exceptions\Repository;
class RecordNotFoundException extends \Exception class RecordNotFoundException extends RepositoryException
{ {
/** /**
* @return int * Handle request to render this exception to a user. Returns the default
* 404 page view.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/ */
public function getStatusCode() public function render($request)
{ {
return 404; if (! config('app.debug')) {
return response()->view('errors.404', [], 404);
}
} }
} }

View file

@ -1,14 +1,9 @@
<?php <?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\Repository; namespace Pterodactyl\Exceptions\Repository;
class RepositoryException extends \Exception use Pterodactyl\Exceptions\PterodactylException;
class RepositoryException extends PterodactylException
{ {
} }

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Exceptions\Service\Allocation;
use Pterodactyl\Exceptions\DisplayException;
class ServerUsingAllocationException extends DisplayException
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Exceptions\Service\Deployment;
use Pterodactyl\Exceptions\DisplayException;
class NoViableAllocationException extends DisplayException
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Exceptions\Service\Deployment;
use Pterodactyl\Exceptions\DisplayException;
class NoViableNodeException extends DisplayException
{
}

View file

@ -1,16 +1,17 @@
<?php <?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; namespace Pterodactyl\Exceptions\Service;
use Illuminate\Http\Response;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
class HasActiveServersException extends DisplayException class HasActiveServersException extends DisplayException
{ {
/**
* @return int
*/
public function getStatusCode()
{
return Response::HTTP_BAD_REQUEST;
}
} }

View file

@ -1,16 +1,17 @@
<?php <?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\Location; namespace Pterodactyl\Exceptions\Service\Location;
use Illuminate\Http\Response;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
class HasActiveNodesException extends DisplayException class HasActiveNodesException extends DisplayException
{ {
/**
* @return int
*/
public function getStatusCode()
{
return Response::HTTP_BAD_REQUEST;
}
} }

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Exceptions\Service\Node;
use Pterodactyl\Exceptions\DisplayException;
class ConfigurationNotPersistedException extends DisplayException
{
}

View file

@ -0,0 +1,72 @@
<?php
namespace Pterodactyl\Extensions\League\Fractal\Serializers;
use League\Fractal\Serializer\ArraySerializer;
class PterodactylSerializer extends ArraySerializer
{
/**
* Serialize an item.
*
* @param string $resourceKey
* @param array $data
* @return array
*/
public function item($resourceKey, array $data)
{
return [
'object' => $resourceKey,
'attributes' => $data,
];
}
/**
* Serialize a collection.
*
* @param string $resourceKey
* @param array $data
* @return array
*/
public function collection($resourceKey, array $data)
{
$response = [];
foreach ($data as $datum) {
$response[] = $this->item($resourceKey, $datum);
}
return [
'object' => 'list',
'data' => $response,
];
}
/**
* Serialize a null resource.
*
* @return array
*/
public function null()
{
return [
'object' => 'null_resource',
'attributes' => null,
];
}
/**
* Merge the included resources with the parent resource being serialized.
*
* @param array $transformedData
* @param array $includedData
* @return array
*/
public function mergeIncludes($transformedData, $includedData)
{
foreach ($includedData as $key => $datum) {
$transformedData['relationships'][$key] = $datum;
}
return $transformedData;
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Pterodactyl\Extensions\Spatie\Fractalistic;
use League\Fractal\TransformerAbstract;
use Spatie\Fractal\Fractal as SpatieFractal;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use Pterodactyl\Extensions\League\Fractal\Serializers\PterodactylSerializer;
class Fractal extends SpatieFractal
{
/**
* Create fractal data.
*
* @return \League\Fractal\Scope
*
* @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation
* @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified
*/
public function createData()
{
// Set the serializer by default.
if (is_null($this->serializer)) {
$this->serializer = new PterodactylSerializer;
}
// Automatically set the paginator on the response object if the
// data being provided implements a paginator.
if (is_null($this->paginator) && $this->data instanceof LengthAwarePaginator) {
$this->paginator = new IlluminatePaginatorAdapter($this->data);
}
// If the resource name is not set attempt to pull it off the transformer
// itself and set it automatically.
if (
is_null($this->resourceName)
&& $this->transformer instanceof TransformerAbstract
&& method_exists($this->transformer, 'getResourceName')
) {
$this->resourceName = $this->transformer->getResourceName();
}
return parent::createData();
}
}

View file

@ -1,6 +1,6 @@
<?php <?php
namespace Pterodactyl\Http\Controllers\API\Remote; namespace Pterodactyl\Http\Controllers\Api\Remote;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View file

@ -7,7 +7,7 @@
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
namespace Pterodactyl\Http\Controllers\API\Remote; namespace Pterodactyl\Http\Controllers\Api\Remote;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;

View file

@ -1,6 +1,6 @@
<?php <?php
namespace Pterodactyl\Http\Controllers\API\Remote; namespace Pterodactyl\Http\Controllers\Api\Remote;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -9,7 +9,7 @@ use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins; use Illuminate\Foundation\Auth\ThrottlesLogins;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Services\Sftp\AuthenticateUsingPasswordService; use Pterodactyl\Services\Sftp\AuthenticateUsingPasswordService;
use Pterodactyl\Http\Requests\API\Remote\SftpAuthenticationFormRequest; use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest;
class SftpController extends Controller class SftpController extends Controller
{ {
@ -34,7 +34,7 @@ class SftpController extends Controller
* Authenticate a set of credentials and return the associated server details * Authenticate a set of credentials and return the associated server details
* for a SFTP connection on the daemon. * for a SFTP connection on the daemon.
* *
* @param \Pterodactyl\Http\Requests\API\Remote\SftpAuthenticationFormRequest $request * @param \Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest $request
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException

View file

@ -22,7 +22,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
namespace Pterodactyl\Http\Controllers\API\Remote; namespace Pterodactyl\Http\Controllers\Api\Remote;
use Spatie\Fractal\Fractal; use Spatie\Fractal\Fractal;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;

View file

@ -0,0 +1,117 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin;
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\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Api\KeyCreationService;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
use Pterodactyl\Http\Requests\Admin\Api\StoreApplicationApiKeyRequest;
class ApiController extends Controller
{
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
private $alert;
/**
* @var \Pterodactyl\Services\Api\KeyCreationService
*/
private $keyCreationService;
/**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
*/
private $repository;
/**
* ApplicationApiController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
* @param \Pterodactyl\Services\Api\KeyCreationService $keyCreationService
*/
public function __construct(
AlertsMessageBag $alert,
ApiKeyRepositoryInterface $repository,
KeyCreationService $keyCreationService
) {
$this->alert = $alert;
$this->keyCreationService = $keyCreationService;
$this->repository = $repository;
}
/**
* Render view showing all of a user's application API keys.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request): View
{
return view('admin.api.index', [
'keys' => $this->repository->getApplicationKeys($request->user()),
]);
}
/**
* Render view allowing an admin to create a new application API key.
*
* @return \Illuminate\View\View
*/
public function create(): View
{
$resources = AdminAcl::getResourceList();
sort($resources);
return view('admin.api.new', [
'resources' => $resources,
'permissions' => [
'r' => AdminAcl::READ,
'rw' => AdminAcl::READ | AdminAcl::WRITE,
'n' => AdminAcl::NONE,
],
]);
}
/**
* Store the new key and redirect the user back to the application key listing.
*
* @param \Pterodactyl\Http\Requests\Admin\Api\StoreApplicationApiKeyRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(StoreApplicationApiKeyRequest $request): RedirectResponse
{
$this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([
'memo' => $request->input('memo'),
'user_id' => $request->user()->id,
], $request->getKeyPermissions());
$this->alert->success('A new application API key has been generated for your account.')->flash();
return redirect()->route('admin.api.index');
}
/**
* Delete an application API key from the database.
*
* @param \Illuminate\Http\Request $request
* @param string $identifier
* @return \Illuminate\Http\Response
*/
public function delete(Request $request, string $identifier): Response
{
$this->repository->deleteApplicationKey($request->user(), $identifier);
return response('', 204);
}
}

View file

@ -12,6 +12,8 @@ namespace Pterodactyl\Http\Controllers\Admin;
use Javascript; use Javascript;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\Node; use Pterodactyl\Models\Node;
use Illuminate\Http\Response;
use Pterodactyl\Models\Allocation;
use Prologue\Alerts\AlertsMessageBag; use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Nodes\NodeUpdateService; use Pterodactyl\Services\Nodes\NodeUpdateService;
@ -23,6 +25,7 @@ use Pterodactyl\Services\Helpers\SoftwareVersionService;
use Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest; use Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest; use Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest;
use Pterodactyl\Services\Allocations\AllocationDeletionService;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Http\Requests\Admin\Node\AllocationAliasFormRequest; use Pterodactyl\Http\Requests\Admin\Node\AllocationAliasFormRequest;
@ -78,11 +81,16 @@ class NodesController extends Controller
* @var \Pterodactyl\Services\Helpers\SoftwareVersionService * @var \Pterodactyl\Services\Helpers\SoftwareVersionService
*/ */
protected $versionService; protected $versionService;
/**
* @var \Pterodactyl\Services\Allocations\AllocationDeletionService
*/
private $allocationDeletionService;
/** /**
* NodesController constructor. * NodesController constructor.
* *
* @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Services\Allocations\AllocationDeletionService $allocationDeletionService
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
* @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService * @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService
* @param \Illuminate\Cache\Repository $cache * @param \Illuminate\Cache\Repository $cache
@ -95,6 +103,7 @@ class NodesController extends Controller
*/ */
public function __construct( public function __construct(
AlertsMessageBag $alert, AlertsMessageBag $alert,
AllocationDeletionService $allocationDeletionService,
AllocationRepositoryInterface $allocationRepository, AllocationRepositoryInterface $allocationRepository,
AssignmentService $assignmentService, AssignmentService $assignmentService,
CacheRepository $cache, CacheRepository $cache,
@ -106,6 +115,7 @@ class NodesController extends Controller
SoftwareVersionService $versionService SoftwareVersionService $versionService
) { ) {
$this->alert = $alert; $this->alert = $alert;
$this->allocationDeletionService = $allocationDeletionService;
$this->allocationRepository = $allocationRepository; $this->allocationRepository = $allocationRepository;
$this->assignmentService = $assignmentService; $this->assignmentService = $assignmentService;
$this->cache = $cache; $this->cache = $cache;
@ -262,17 +272,14 @@ class NodesController extends Controller
/** /**
* Removes a single allocation from a node. * Removes a single allocation from a node.
* *
* @param int $node * @param \Pterodactyl\Models\Allocation $allocation
* @param int $allocation * @return \Illuminate\Http\Response
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse *
* @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
*/ */
public function allocationRemoveSingle($node, $allocation) public function allocationRemoveSingle(Allocation $allocation): Response
{ {
$this->allocationRepository->deleteWhere([ $this->allocationDeletionService->handle($allocation);
['id', '=', $allocation],
['node_id', '=', $node],
['server_id', '=', null],
]);
return response('', 204); return response('', 204);
} }

View file

@ -251,14 +251,17 @@ class ServersController extends Controller
* @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request * @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* *
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
*/ */
public function store(ServerFormRequest $request) public function store(ServerFormRequest $request)
{ {
$server = $this->service->create($request->except('_token')); $server = $this->service->handle($request->except('_token'));
$this->alert->success(trans('admin/server.alerts.server_created'))->flash(); $this->alert->success(trans('admin/server.alerts.server_created'))->flash();
return redirect()->route('admin.servers.view', $server->id); return redirect()->route('admin.servers.view', $server->id);
@ -401,7 +404,7 @@ class ServersController extends Controller
*/ */
public function setDetails(Request $request, Server $server) public function setDetails(Request $request, Server $server)
{ {
$this->detailsModificationService->edit($server, $request->only([ $this->detailsModificationService->handle($server, $request->only([
'owner_id', 'name', 'description', 'owner_id', 'name', 'description',
])); ]));

View file

@ -0,0 +1,76 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Container\Container;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Extensions\Spatie\Fractalistic\Fractal;
abstract class ApplicationApiController extends Controller
{
/**
* @var \Illuminate\Http\Request
*/
private $request;
/**
* @var \Pterodactyl\Extensions\Spatie\Fractalistic\Fractal
*/
protected $fractal;
/**
* ApplicationApiController constructor.
*/
public function __construct()
{
Container::getInstance()->call([$this, 'loadDependencies']);
// Parse all of the includes to use on this request.
$includes = collect(explode(',', $this->request->input('include', '')))->map(function ($value) {
return trim($value);
})->filter()->toArray();
$this->fractal->parseIncludes($includes);
$this->fractal->limitRecursion(2);
}
/**
* Perform dependency injection of certain classes needed for core functionality
* without littering the constructors of classes that extend this abstract.
*
* @param \Pterodactyl\Extensions\Spatie\Fractalistic\Fractal $fractal
* @param \Illuminate\Http\Request $request
*/
public function loadDependencies(Fractal $fractal, Request $request)
{
$this->fractal = $fractal;
$this->request = $request;
}
/**
* Return an instance of an application transformer.
*
* @param string $abstract
* @return \Pterodactyl\Transformers\Api\Application\BaseTransformer
*/
public function getTransformer(string $abstract)
{
/** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */
$transformer = Container::getInstance()->make($abstract);
$transformer->setKey($this->request->attributes->get('api_key'));
return $transformer;
}
/**
* Return a HTTP/204 response for the API.
*
* @return \Illuminate\Http\Response
*/
protected function returnNoContent(): Response
{
return new Response('', Response::HTTP_NO_CONTENT);
}
}

View file

@ -0,0 +1,145 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Locations;
use Illuminate\Http\Response;
use Pterodactyl\Models\Location;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Services\Locations\LocationUpdateService;
use Pterodactyl\Services\Locations\LocationCreationService;
use Pterodactyl\Services\Locations\LocationDeletionService;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest;
use Pterodactyl\Http\Requests\Api\Application\Locations\DeleteLocationRequest;
use Pterodactyl\Http\Requests\Api\Application\Locations\UpdateLocationRequest;
class LocationController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Services\Locations\LocationCreationService
*/
private $creationService;
/**
* @var \Pterodactyl\Services\Locations\LocationDeletionService
*/
private $deletionService;
/**
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface
*/
private $repository;
/**
* @var \Pterodactyl\Services\Locations\LocationUpdateService
*/
private $updateService;
/**
* LocationController constructor.
*
* @param \Pterodactyl\Services\Locations\LocationCreationService $creationService
* @param \Pterodactyl\Services\Locations\LocationDeletionService $deletionService
* @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $repository
* @param \Pterodactyl\Services\Locations\LocationUpdateService $updateService
*/
public function __construct(
LocationCreationService $creationService,
LocationDeletionService $deletionService,
LocationRepositoryInterface $repository,
LocationUpdateService $updateService
) {
parent::__construct();
$this->creationService = $creationService;
$this->deletionService = $deletionService;
$this->repository = $repository;
$this->updateService = $updateService;
}
/**
* Return all of the locations currently registered on the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest $request
* @return array
*/
public function index(GetLocationsRequest $request): array
{
$locations = $this->repository->paginated(100);
return $this->fractal->collection($locations)
->transformWith($this->getTransformer(LocationTransformer::class))
->toArray();
}
/**
* Return a single location.
*
* @param \Pterodactyl\Http\Controllers\Api\Application\Locations\GetLocationRequest $request
* @return array
*/
public function view(GetLocationRequest $request): array
{
return $this->fractal->item($request->getModel(Location::class))
->transformWith($this->getTransformer(LocationTransformer::class))
->toArray();
}
/**
* Store a new location on the Panel and return a HTTP/201 response code with the
* new location attached.
*
* @param \Pterodactyl\Http\Controllers\Api\Application\Locations\StoreLocationRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(StoreLocationRequest $request): JsonResponse
{
$location = $this->creationService->handle($request->validated());
return $this->fractal->item($location)
->transformWith($this->getTransformer(LocationTransformer::class))
->addMeta([
'resource' => route('api.application.locations.view', [
'location' => $location->id,
]),
])
->respond(201);
}
/**
* Update a location on the Panel and return the updated record to the user.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Locations\UpdateLocationRequest $request
* @return array
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(UpdateLocationRequest $request): array
{
$location = $this->updateService->handle($request->getModel(Location::class), $request->validated());
return $this->fractal->item($location)
->transformWith($this->getTransformer(LocationTransformer::class))
->toArray();
}
/**
* Delete a location from the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Locations\DeleteLocationRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException
*/
public function delete(DeleteLocationRequest $request): Response
{
$this->deletionService->handle($request->getModel(Location::class));
return response('', 204);
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Nests;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Nest;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Transformers\Api\Application\EggTransformer;
use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggsRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class EggController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface
*/
private $repository;
/**
* EggController constructor.
*
* @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository
*/
public function __construct(EggRepositoryInterface $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Return all eggs that exist for a given nest.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggsRequest $request
* @return array
*/
public function index(GetEggsRequest $request): array
{
$eggs = $this->repository->findWhere([
['nest_id', '=', $request->getModel(Nest::class)->id],
]);
return $this->fractal->collection($eggs)
->transformWith($this->getTransformer(EggTransformer::class))
->toArray();
}
/**
* Return a single egg that exists on the specified nest.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggRequest $request
* @return array
*/
public function view(GetEggRequest $request): array
{
return $this->fractal->item($request->getModel(Egg::class))
->transformWith($this->getTransformer(EggTransformer::class))
->toArray();
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Nests;
use Pterodactyl\Models\Nest;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
use Pterodactyl\Transformers\Api\Application\NestTransformer;
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class NestController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface
*/
private $repository;
/**
* NestController constructor.
*
* @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository
*/
public function __construct(NestRepositoryInterface $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Return all Nests that exist on the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest $request
* @return array
*/
public function index(GetNestsRequest $request): array
{
$nests = $this->repository->paginated(50);
return $this->fractal->collection($nests)
->transformWith($this->getTransformer(NestTransformer::class))
->toArray();
}
/**
* Return information about a single Nest model.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest $request
* @return array
*/
public function view(GetNestsRequest $request): array
{
return $this->fractal->item($request->getModel(Nest::class))
->transformWith($this->getTransformer(NestTransformer::class))
->toArray();
}
}

View file

@ -0,0 +1,99 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
use Pterodactyl\Models\Node;
use Illuminate\Http\Response;
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;
use Pterodactyl\Http\Requests\Api\Application\Allocations\StoreAllocationRequest;
use Pterodactyl\Http\Requests\Api\Application\Allocations\DeleteAllocationRequest;
class AllocationController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Services\Allocations\AssignmentService
*/
private $assignmentService;
/**
* @var \Pterodactyl\Services\Allocations\AllocationDeletionService
*/
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
) {
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
* @return array
*/
public function index(GetAllocationsRequest $request): array
{
$allocations = $this->repository->getPaginatedAllocationsForNode(
$request->getModel(Node::class)->id, 50
);
return $this->fractal->collection($allocations)
->transformWith($this->getTransformer(AllocationTransformer::class))
->toArray();
}
/**
* Store new allocations for a given node.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Allocations\StoreAllocationRequest $request
* @return array
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function store(StoreAllocationRequest $request): array
{
$this->assignmentService->handle($request->getModel(Node::class), $request->validated());
return response('', 204);
}
/**
* Delete a specific allocation from the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Allocations\DeleteAllocationRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
*/
public function delete(DeleteAllocationRequest $request): Response
{
$this->deletionService->handle($request->getModel(Allocation::class));
return response('', 204);
}
}

View file

@ -0,0 +1,151 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
use Pterodactyl\Models\Node;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Services\Nodes\NodeUpdateService;
use Pterodactyl\Services\Nodes\NodeCreationService;
use Pterodactyl\Services\Nodes\NodeDeletionService;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Transformers\Api\Application\NodeTransformer;
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest;
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest;
use Pterodactyl\Http\Requests\Api\Application\Nodes\StoreNodeRequest;
use Pterodactyl\Http\Requests\Api\Application\Nodes\DeleteNodeRequest;
use Pterodactyl\Http\Requests\Api\Application\Nodes\UpdateNodeRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class NodeController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Services\Nodes\NodeCreationService
*/
private $creationService;
/**
* @var \Pterodactyl\Services\Nodes\NodeDeletionService
*/
private $deletionService;
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
*/
private $repository;
/**
* @var \Pterodactyl\Services\Nodes\NodeUpdateService
*/
private $updateService;
/**
* NodeController constructor.
*
* @param \Pterodactyl\Services\Nodes\NodeCreationService $creationService
* @param \Pterodactyl\Services\Nodes\NodeDeletionService $deletionService
* @param \Pterodactyl\Services\Nodes\NodeUpdateService $updateService
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
*/
public function __construct(
NodeCreationService $creationService,
NodeDeletionService $deletionService,
NodeUpdateService $updateService,
NodeRepositoryInterface $repository
) {
parent::__construct();
$this->repository = $repository;
$this->creationService = $creationService;
$this->deletionService = $deletionService;
$this->updateService = $updateService;
}
/**
* Return all of the nodes currently available on the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest $request
* @return array
*/
public function index(GetNodesRequest $request): array
{
$nodes = $this->repository->paginated(50);
return $this->fractal->collection($nodes)
->transformWith($this->getTransformer(NodeTransformer::class))
->toArray();
}
/**
* Return data for a single instance of a node.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest $request
* @return array
*/
public function view(GetNodeRequest $request): array
{
return $this->fractal->item($request->getModel(Node::class))
->transformWith($this->getTransformer(NodeTransformer::class))
->toArray();
}
/**
* Create a new node on the Panel. Returns the created node and a HTTP/201
* status response on success.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nodes\StoreNodeRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(StoreNodeRequest $request): JsonResponse
{
$node = $this->creationService->handle($request->validated());
return $this->fractal->item($node)
->transformWith($this->getTransformer(NodeTransformer::class))
->addMeta([
'resource' => route('api.application.nodes.view', [
'node' => $node->id,
]),
])
->respond(201);
}
/**
* Update an existing node on the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nodes\UpdateNodeRequest $request
* @return array
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(UpdateNodeRequest $request): array
{
$node = $this->updateService->returnUpdatedModel()->handle(
$request->getModel(Node::class), $request->validated()
);
return $this->fractal->item($node)
->transformWith($this->getTransformer(NodeTransformer::class))
->toArray();
}
/**
* Deletes a given node from the Panel as long as there are no servers
* currently attached to it.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nodes\DeleteNodeRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
*/
public function delete(DeleteNodeRequest $request): Response
{
$this->deletionService->handle($request->getModel(Node::class));
return response('', 204);
}
}

View file

@ -0,0 +1,140 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
use Illuminate\Http\Response;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Database;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Services\Databases\DatabasePasswordService;
use Pterodactyl\Services\Databases\DatabaseManagementService;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Transformers\Api\Application\ServerDatabaseTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest;
class DatabaseController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Services\Databases\DatabaseManagementService
*/
private $databaseManagementService;
/**
* @var \Pterodactyl\Services\Databases\DatabasePasswordService
*/
private $databasePasswordService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
*/
private $repository;
/**
* DatabaseController constructor.
*
* @param \Pterodactyl\Services\Databases\DatabaseManagementService $databaseManagementService
* @param \Pterodactyl\Services\Databases\DatabasePasswordService $databasePasswordService
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
*/
public function __construct(
DatabaseManagementService $databaseManagementService,
DatabasePasswordService $databasePasswordService,
DatabaseRepositoryInterface $repository
) {
parent::__construct();
$this->databaseManagementService = $databaseManagementService;
$this->databasePasswordService = $databasePasswordService;
$this->repository = $repository;
}
/**
* Return a listing of all databases currently available to a single
* server.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request
* @return array
*/
public function index(GetServerDatabasesRequest $request): array
{
$databases = $this->repository->getDatabasesForServer($request->getModel(Server::class)->id);
return $this->fractal->collection($databases)
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
->toArray();
}
/**
* Return a single server database.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest $request
* @return array
*/
public function view(GetServerDatabaseRequest $request): array
{
return $this->fractal->item($request->getModel(Database::class))
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
->toArray();
}
/**
* Reset the password for a specific server database.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function resetPassword(ServerDatabaseWriteRequest $request): Response
{
$this->databasePasswordService->handle($request->getModel(Database::class), str_random(24));
return response('', 204);
}
/**
* Create a new database on the Panel for a given server.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Exception
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(StoreServerDatabaseRequest $request): JsonResponse
{
$server = $request->getModel(Server::class);
$database = $this->databaseManagementService->create($server->id, $request->validated());
return $this->fractal->item($database)
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
->addMeta([
'resource' => route('api.application.servers.databases.view', [
'server' => $server->id,
'database' => $database->id,
]),
])
->respond(201);
}
/**
* Handle a request to delete a specific server database from the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function delete(ServerDatabaseWriteRequest $request): Response
{
$this->databaseManagementService->delete($request->getModel(Database::class)->id);
return response('', 204);
}
}

View file

@ -0,0 +1,119 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
use Illuminate\Http\Response;
use Pterodactyl\Models\Server;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Services\Servers\ServerCreationService;
use Pterodactyl\Services\Servers\ServerDeletionService;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class ServerController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Services\Servers\ServerCreationService
*/
private $creationService;
/**
* @var \Pterodactyl\Services\Servers\ServerDeletionService
*/
private $deletionService;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
private $repository;
/**
* ServerController constructor.
*
* @param \Pterodactyl\Services\Servers\ServerCreationService $creationService
* @param \Pterodactyl\Services\Servers\ServerDeletionService $deletionService
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(
ServerCreationService $creationService,
ServerDeletionService $deletionService,
ServerRepositoryInterface $repository
) {
parent::__construct();
$this->creationService = $creationService;
$this->deletionService = $deletionService;
$this->repository = $repository;
}
/**
* Return all of the servers that currently exist on the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest $request
* @return array
*/
public function index(GetServersRequest $request): array
{
$servers = $this->repository->setSearchTerm($request->input('search'))->paginated(50);
return $this->fractal->collection($servers)
->transformWith($this->getTransformer(ServerTransformer::class))
->toArray();
}
/**
* Create a new server on the system.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @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(StoreServerRequest $request): JsonResponse
{
$server = $this->creationService->handle($request->validated(), $request->getDeploymentObject());
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->respond(201);
}
/**
* Show a single server transformed for the application API.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request
* @return array
*/
public function view(ServerWriteRequest $request): array
{
return $this->fractal->item($request->getModel(Server::class))
->transformWith($this->getTransformer(ServerTransformer::class))
->toArray();
}
/**
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request
* @param \Pterodactyl\Models\Server $server
* @param string $force
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function delete(ServerWriteRequest $request, Server $server, string $force = ''): Response
{
$this->deletionService->withForce($force === 'force')->handle($server);
return $this->returnNoContent();
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
use Pterodactyl\Models\Server;
use Pterodactyl\Services\Servers\BuildModificationService;
use Pterodactyl\Services\Servers\DetailsModificationService;
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerDetailsRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerBuildConfigurationRequest;
class ServerDetailsController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Services\Servers\BuildModificationService
*/
private $buildModificationService;
/**
* @var \Pterodactyl\Services\Servers\DetailsModificationService
*/
private $detailsModificationService;
/**
* ServerDetailsController constructor.
*
* @param \Pterodactyl\Services\Servers\BuildModificationService $buildModificationService
* @param \Pterodactyl\Services\Servers\DetailsModificationService $detailsModificationService
*/
public function __construct(
BuildModificationService $buildModificationService,
DetailsModificationService $detailsModificationService
) {
parent::__construct();
$this->buildModificationService = $buildModificationService;
$this->detailsModificationService = $detailsModificationService;
}
/**
* Update the details for a specific server.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerDetailsRequest $request
* @return array
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function details(UpdateServerDetailsRequest $request): array
{
$server = $this->detailsModificationService->returnUpdatedModel()->handle(
$request->getModel(Server::class), $request->validated()
);
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->toArray();
}
/**
* Update the build details for a specific server.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerBuildConfigurationRequest $request
* @return array
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function build(UpdateServerBuildConfigurationRequest $request): array
{
$server = $this->buildModificationService->handle($request->getModel(Server::class), $request->validated());
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->toArray();
}
}

View file

@ -0,0 +1,114 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
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
*/
private $reinstallServerService;
/**
* @var \Pterodactyl\Services\Servers\SuspensionService
*/
private $suspensionService;
/**
* 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;
}
/**
* Suspend a server on the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function suspend(ServerWriteRequest $request): Response
{
$this->suspensionService->toggle($request->getModel(Server::class), SuspensionService::ACTION_SUSPEND);
return $this->returnNoContent();
}
/**
* Unsuspend a server on the Panel.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function unsuspend(ServerWriteRequest $request): Response
{
$this->suspensionService->toggle($request->getModel(Server::class), SuspensionService::ACTION_UNSUSPEND);
return $this->returnNoContent();
}
/**
* Mark a server as needing to be reinstalled.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function reinstall(ServerWriteRequest $request): 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));
return $this->returnNoContent();
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
use Pterodactyl\Models\Server;
use Pterodactyl\Services\Servers\StartupModificationService;
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerStartupRequest;
class StartupController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Services\Servers\StartupModificationService
*/
private $modificationService;
/**
* StartupController constructor.
*
* @param \Pterodactyl\Services\Servers\StartupModificationService $modificationService
*/
public function __construct(StartupModificationService $modificationService)
{
parent::__construct();
$this->modificationService = $modificationService;
}
/**
* Update the startup and environment settings for a specific server.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerStartupRequest $request
* @return array
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function index(UpdateServerStartupRequest $request): array
{
$server = $this->modificationService->handle($request->getModel(Server::class), $request->validated());
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->toArray();
}
}

View file

@ -0,0 +1,179 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Users;
use Pterodactyl\Models\User;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Services\Users\UserUpdateService;
use Pterodactyl\Services\Users\UserCreationService;
use Pterodactyl\Services\Users\UserDeletionService;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Transformers\Api\Application\UserTransformer;
use Pterodactyl\Http\Requests\Api\Application\Users\GetUserRequest;
use Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest;
use Pterodactyl\Http\Requests\Api\Application\Users\StoreUserRequest;
use Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest;
use Pterodactyl\Http\Requests\Api\Application\Users\UpdateUserRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class UserController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Services\Users\UserCreationService
*/
private $creationService;
/**
* @var \Pterodactyl\Services\Users\UserDeletionService
*/
private $deletionService;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
*/
private $repository;
/**
* @var \Pterodactyl\Services\Users\UserUpdateService
*/
private $updateService;
/**
* UserController constructor.
*
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
* @param \Pterodactyl\Services\Users\UserCreationService $creationService
* @param \Pterodactyl\Services\Users\UserDeletionService $deletionService
* @param \Pterodactyl\Services\Users\UserUpdateService $updateService
*/
public function __construct(
UserRepositoryInterface $repository,
UserCreationService $creationService,
UserDeletionService $deletionService,
UserUpdateService $updateService
) {
parent::__construct();
$this->creationService = $creationService;
$this->deletionService = $deletionService;
$this->repository = $repository;
$this->updateService = $updateService;
}
/**
* Handle request to list all users on the panel. Returns a JSON-API representation
* of a collection of users including any defined relations passed in
* the request.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest $request
* @return array
*/
public function index(GetUsersRequest $request): array
{
$users = $this->repository->paginated(100);
return $this->fractal->collection($users)
->transformWith($this->getTransformer(UserTransformer::class))
->toArray();
}
/**
* Handle a request to view a single user. Includes any relations that
* were defined in the request.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Users\GetUserRequest $request
* @return array
*/
public function view(GetUserRequest $request): array
{
return $this->fractal->item($request->getModel(User::class))
->transformWith($this->getTransformer(UserTransformer::class))
->toArray();
}
/**
* Update an existing user on the system and return the response. Returns the
* updated user model response on success. Supports handling of token revocation
* errors when switching a user from an admin to a normal user.
*
* Revocation errors are returned under the 'revocation_errors' key in the response
* meta. If there are no errors this is an empty array.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Users\UpdateUserRequest $request
* @return array
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(UpdateUserRequest $request): array
{
$this->updateService->setUserLevel(User::USER_LEVEL_ADMIN);
$collection = $this->updateService->handle($request->getModel(User::class), $request->validated());
$errors = [];
if (! empty($collection->get('exceptions'))) {
foreach ($collection->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(),
]);
$errors[] = ['message' => $message, 'node' => $node];
}
}
$response = $this->fractal->item($collection->get('model'))
->transformWith($this->getTransformer(UserTransformer::class));
if (count($errors) > 0) {
$response->addMeta([
'revocation_errors' => $errors,
]);
}
return $response->toArray();
}
/**
* Store a new user on the system. Returns the created user and a HTTP/201
* header on successful creation.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Users\StoreUserRequest $request
* @return \Illuminate\Http\JsonResponse
*
* @throws \Exception
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(StoreUserRequest $request): JsonResponse
{
$user = $this->creationService->handle($request->validated());
return $this->fractal->item($user)
->transformWith($this->getTransformer(UserTransformer::class))
->addMeta([
'resource' => route('api.application.users.view', [
'user' => $user->id,
]),
])
->respond(201);
}
/**
* Handle a request to delete a user from the Panel. Returns a HTTP/204 response
* on successful deletion.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function delete(DeleteUserRequest $request): Response
{
$this->deletionService->handle($request->getModel(User::class));
return response('', 204);
}
}

View file

@ -2,15 +2,17 @@
namespace Pterodactyl\Http\Controllers\Base; namespace Pterodactyl\Http\Controllers\Base;
use Illuminate\View\View;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pterodactyl\Models\ApiKey;
use Prologue\Alerts\AlertsMessageBag; use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Models\APIPermission;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Api\KeyCreationService; use Pterodactyl\Services\Api\KeyCreationService;
use Pterodactyl\Http\Requests\Base\ApiKeyFormRequest; use Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
class APIController extends Controller class AccountKeyController extends Controller
{ {
/** /**
* @var \Prologue\Alerts\AlertsMessageBag * @var \Prologue\Alerts\AlertsMessageBag
@ -45,57 +47,44 @@ class APIController extends Controller
} }
/** /**
* Display base API index page. * Display a listing of all account API keys.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View * @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/ */
public function index(Request $request) public function index(Request $request): View
{ {
return view('base.api.index', [ return view('base.api.index', [
'keys' => $this->repository->findWhere([['user_id', '=', $request->user()->id]]), 'keys' => $this->repository->getAccountKeys($request->user()),
]); ]);
} }
/** /**
* Display API key creation page. * Display account API key creation page.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function create(Request $request) public function create(Request $request): View
{ {
return view('base.api.new', [ return view('base.api.new');
'permissions' => [
'user' => collect(APIPermission::CONST_PERMISSIONS)->pull('_user'),
'admin' => ! $request->user()->root_admin ? null : collect(APIPermission::CONST_PERMISSIONS)->except('_user')->toArray(),
],
]);
} }
/** /**
* Handle saving new API key. * Handle saving new account API key.
* *
* @param \Pterodactyl\Http\Requests\Base\ApiKeyFormRequest $request * @param \Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
* *
* @throws \Exception
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/ */
public function store(ApiKeyFormRequest $request) public function store(StoreAccountKeyRequest $request)
{ {
$adminPermissions = []; $this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([
if ($request->user()->root_admin) {
$adminPermissions = $request->input('admin_permissions', []);
}
$secret = $this->keyService->handle([
'user_id' => $request->user()->id, 'user_id' => $request->user()->id,
'allowed_ips' => $request->input('allowed_ips'), 'allowed_ips' => $request->input('allowed_ips'),
'memo' => $request->input('memo'), 'memo' => $request->input('memo'),
], $request->input('permissions', []), $adminPermissions); ]);
$this->alert->success(trans('base.api.index.keypair_created'))->flash(); $this->alert->success(trans('base.api.index.keypair_created'))->flash();
@ -103,18 +92,15 @@ class APIController extends Controller
} }
/** /**
* @param \Illuminate\Http\Request $request * Delete an account API key from the Panel via an AJAX request.
* @param string $key
* @return \Illuminate\Http\Response
* *
* @throws \Exception * @param \Illuminate\Http\Request $request
* @param string $identifier
* @return \Illuminate\Http\Response
*/ */
public function revoke(Request $request, $key) public function revoke(Request $request, string $identifier): Response
{ {
$this->repository->deleteWhere([ $this->repository->deleteAccountKey($request->user(), $identifier);
['user_id', '=', $request->user()->id],
['token', '=', $key],
]);
return response('', 204); return response('', 204);
} }

View file

@ -68,7 +68,7 @@ class RemoteRequestController extends Controller
try { try {
$listing = $this->repository->setServer($server)->setToken($request->attributes->get('server_token'))->getDirectory($requestDirectory); $listing = $this->repository->setServer($server)->setToken($request->attributes->get('server_token'))->getDirectory($requestDirectory);
} catch (RequestException $exception) { } catch (RequestException $exception) {
throw new DaemonConnectionException($exception); throw new DaemonConnectionException($exception, true);
} }
return view('server.files.list', [ return view('server.files.list', [

View file

@ -14,25 +14,26 @@ use Pterodactyl\Http\Middleware\AdminAuthenticate;
use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Routing\Middleware\ThrottleRequests;
use Pterodactyl\Http\Middleware\LanguageMiddleware; use Pterodactyl\Http\Middleware\LanguageMiddleware;
use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Pterodactyl\Http\Middleware\API\AuthenticateKey;
use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Routing\Middleware\SubstituteBindings;
use Pterodactyl\Http\Middleware\AccessingValidServer; use Pterodactyl\Http\Middleware\AccessingValidServer;
use Pterodactyl\Http\Middleware\API\SetSessionDriver;
use Illuminate\View\Middleware\ShareErrorsFromSession; use Illuminate\View\Middleware\ShareErrorsFromSession;
use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; use Pterodactyl\Http\Middleware\RedirectIfAuthenticated;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Pterodactyl\Http\Middleware\API\AuthenticateIPAccess; use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings;
use Pterodactyl\Http\Middleware\Daemon\DaemonAuthenticate;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Pterodactyl\Http\Middleware\API\HasPermissionToResource;
use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser; use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser;
use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate;
use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer; use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer;
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer;
use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer;
use Pterodactyl\Http\Middleware\Api\Application\AuthenticateKey;
use Pterodactyl\Http\Middleware\Api\Application\AuthenticateUser;
use Pterodactyl\Http\Middleware\Api\Application\SetSessionDriver;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Pterodactyl\Http\Middleware\Api\Application\AuthenticateIPAccess;
use Pterodactyl\Http\Middleware\DaemonAuthenticate as OldDaemonAuthenticate; use Pterodactyl\Http\Middleware\DaemonAuthenticate as OldDaemonAuthenticate;
class Kernel extends HttpKernel class Kernel extends HttpKernel
@ -67,10 +68,11 @@ class Kernel extends HttpKernel
RequireTwoFactorAuthentication::class, RequireTwoFactorAuthentication::class,
], ],
'api' => [ 'api' => [
'throttle:60,1', 'throttle:120,1',
SubstituteBindings::class, ApiSubstituteBindings::class,
SetSessionDriver::class, SetSessionDriver::class,
AuthenticateKey::class, AuthenticateKey::class,
AuthenticateUser::class,
AuthenticateIPAccess::class, AuthenticateIPAccess::class,
], ],
'daemon' => [ 'daemon' => [
@ -98,9 +100,6 @@ class Kernel extends HttpKernel
'bindings' => SubstituteBindings::class, 'bindings' => SubstituteBindings::class,
'recaptcha' => VerifyReCaptcha::class, 'recaptcha' => VerifyReCaptcha::class,
// API specific middleware.
'api..user_level' => HasPermissionToResource::class,
// Server specific middleware (used for authenticating access to resources) // Server specific middleware (used for authenticating access to resources)
// //
// These are only used for individual server authentication, and not gloabl // These are only used for individual server authentication, and not gloabl

View file

@ -1,58 +0,0 @@
<?php
namespace Pterodactyl\Http\Middleware\API;
use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class HasPermissionToResource
{
/**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
*/
private $repository;
/**
* HasPermissionToResource constructor.
*
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
*/
public function __construct(ApiKeyRepositoryInterface $repository)
{
$this->repository = $repository;
}
/**
* Determine if an API key has permission to access the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $role
* @return mixed
*/
public function handle(Request $request, Closure $next, string $role = 'admin')
{
/** @var \Pterodactyl\Models\APIKey $model */
$model = $request->attributes->get('api_key');
if ($role === 'admin' && ! $request->user()->root_admin) {
throw new NotFoundHttpException;
}
$this->repository->loadPermissions($model);
$routeKey = str_replace(['api.', 'admin.'], '', $request->route()->getName());
$count = $model->getRelation('permissions')->filter(function ($permission) use ($routeKey) {
return $routeKey === str_replace('-', '.', $permission->permission);
})->count();
if ($count === 1) {
return $next($request);
}
throw new AccessDeniedHttpException('Cannot access resource without required `' . $routeKey . '` permission.');
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Pterodactyl\Http\Middleware\Api;
use Closure;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Nest;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Database;
use Pterodactyl\Models\Location;
use Pterodactyl\Models\Allocation;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiSubstituteBindings extends SubstituteBindings
{
/**
* Mappings to automatically assign route parameters to a model.
*
* @var array
*/
protected static $mappings = [
'allocation' => Allocation::class,
'database' => Database::class,
'egg' => Egg::class,
'location' => Location::class,
'nest' => Nest::class,
'node' => Node::class,
'server' => Server::class,
];
/**
* Perform substitution of route parameters without triggering
* a 404 error if a model is not found.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$route = $request->route();
foreach (self::$mappings as $key => $model) {
$this->router->model($key, $model);
}
$this->router->substituteBindings($route);
// Attempt to resolve bindings for this route. If one of the models
// cannot be resolved do not immediately return a 404 error. Set a request
// attribute that can be checked in the base API request class to only
// trigger a 404 after validating that the API key making the request is valid
// and even has permission to access the requested resource.
try {
$this->router->substituteImplicitBindings($route);
} catch (ModelNotFoundException $exception) {
$request->attributes->set('is_missing_model', true);
}
return $next($request);
}
/**
* Return the registered mappings.
*
* @return array
*/
public static function getMappings()
{
return self::$mappings;
}
}

View file

@ -1,6 +1,6 @@
<?php <?php
namespace Pterodactyl\Http\Middleware\API; namespace Pterodactyl\Http\Middleware\Api\Application;
use Closure; use Closure;
use IPTools\IP; use IPTools\IP;

View file

@ -1,10 +1,13 @@
<?php <?php
namespace Pterodactyl\Http\Middleware\API; namespace Pterodactyl\Http\Middleware\Api\Application;
use Closure; use Closure;
use Cake\Chronos\Chronos;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\ApiKey;
use Illuminate\Auth\AuthManager; use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Encryption\Encrypter;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
@ -17,6 +20,11 @@ class AuthenticateKey
*/ */
private $auth; private $auth;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
private $encrypter;
/** /**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface * @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
*/ */
@ -27,26 +35,25 @@ class AuthenticateKey
* *
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
* @param \Illuminate\Auth\AuthManager $auth * @param \Illuminate\Auth\AuthManager $auth
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
*/ */
public function __construct( public function __construct(ApiKeyRepositoryInterface $repository, AuthManager $auth, Encrypter $encrypter)
ApiKeyRepositoryInterface $repository, {
AuthManager $auth
) {
$this->auth = $auth; $this->auth = $auth;
$this->encrypter = $encrypter;
$this->repository = $repository; $this->repository = $repository;
} }
/** /**
* Handle an API request by verifying that the provided API key * Handle an API request by verifying that the provided API key
* is in a valid format, and the route being accessed is allowed * is in a valid format and exists in the database.
* for the given key.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
* *
* @throws \Symfony\Component\HttpKernel\Exception\HttpException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/ */
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
@ -54,14 +61,26 @@ class AuthenticateKey
throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']); throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']);
} }
$raw = $request->bearerToken();
$identifier = substr($raw, 0, ApiKey::IDENTIFIER_LENGTH);
$token = substr($raw, ApiKey::IDENTIFIER_LENGTH);
try { try {
$model = $this->repository->findFirstWhere([['token', '=', $request->bearerToken()]]); $model = $this->repository->findFirstWhere([
['identifier', '=', $identifier],
['key_type', '=', ApiKey::TYPE_APPLICATION],
]);
} catch (RecordNotFoundException $exception) { } catch (RecordNotFoundException $exception) {
throw new AccessDeniedHttpException; throw new AccessDeniedHttpException;
} }
if (! hash_equals($this->encrypter->decrypt($model->token), $token)) {
throw new AccessDeniedHttpException;
}
$this->auth->guard()->loginUsingId($model->user_id); $this->auth->guard()->loginUsingId($model->user_id);
$request->attributes->set('api_key', $model); $request->attributes->set('api_key', $model);
$this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => Chronos::now()]);
return $next($request); return $next($request);
} }

View file

@ -0,0 +1,27 @@
<?php
namespace Pterodactyl\Http\Middleware\Api\Application;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AuthenticateUser
{
/**
* Authenticate that the currently authenticated user is an administrator
* and should be allowed to proceede through the application API.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if (is_null($request->user()) || ! $request->user()->root_admin) {
throw new AccessDeniedHttpException;
}
return $next($request);
}
}

View file

@ -1,6 +1,6 @@
<?php <?php
namespace Pterodactyl\Http\Middleware\API; namespace Pterodactyl\Http\Middleware\Api\Application;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;

View file

@ -1,28 +1,6 @@
<?php <?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\Http\Middleware\Daemon; namespace Pterodactyl\Http\Middleware\Api\Daemon;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;

View file

@ -0,0 +1,39 @@
<?php
namespace Pterodactyl\Http\Requests\Admin\Api;
use Pterodactyl\Models\ApiKey;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Admin\AdminFormRequest;
class StoreApplicationApiKeyRequest extends AdminFormRequest
{
/**
* @return array
*/
public function rules()
{
$modelRules = ApiKey::getCreateRules();
return collect(AdminAcl::getResourceList())->mapWithKeys(function ($resource) use ($modelRules) {
return [AdminAcl::COLUMN_IDENTIFER . $resource => $modelRules['r_' . $resource]];
})->merge(['memo' => $modelRules['memo']])->toArray();
}
/**
* @return array
*/
public function attributes()
{
return [
'memo' => 'Description',
];
}
public function getKeyPermissions(): array
{
return collect($this->validated())->filter(function ($value, $key) {
return substr($key, 0, strlen(AdminAcl::COLUMN_IDENTIFER)) === AdminAcl::COLUMN_IDENTIFER;
})->toArray();
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Allocations;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\Allocation;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteAllocationRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_ALLOCATIONS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Determine if the requested allocation exists and belongs to the node that
* is being passed in the URL.
*
* @return bool
*/
public function resourceExists(): bool
{
$node = $this->route()->parameter('node');
$allocation = $this->route()->parameter('allocation');
if ($node instanceof Node && $node->exists) {
if ($allocation instanceof Allocation && $allocation->exists && $allocation->node_id === $node->id) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Allocations;
use Pterodactyl\Models\Node;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetAllocationsRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_ALLOCATIONS;
/**
* @var int
*/
protected $permission = AdminAcl::READ;
/**
* Determine if the node that we are requesting the allocations
* for exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$node = $this->route()->parameter('node');
return $node instanceof Node && $node->exists;
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Allocations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreAllocationRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_ALLOCATIONS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* @return array
*/
public function rules(): array
{
return [
'ip' => 'required|string',
'alias' => 'sometimes|nullable|string|max:255',
'ports' => 'required|array',
'ports.*' => 'string',
];
}
/**
* @return array
*/
public function validated()
{
$data = parent::validated();
return [
'allocation_ip' => $data['ip'],
'allocation_ports' => $data['ports'],
'allocation_alias' => $data['alias'],
];
}
}

View file

@ -0,0 +1,125 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application;
use Pterodactyl\Models\ApiKey;
use Illuminate\Database\Eloquent\Model;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Illuminate\Foundation\Http\FormRequest;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
abstract class ApplicationApiRequest extends FormRequest
{
/**
* The resource that should be checked when performing the authorization
* function for this request.
*
* @var string|null
*/
protected $resource;
/**
* The permission level that a given API key should have for accessing
* the defined $resource during the request cycle.
*
* @var int
*/
protected $permission = AdminAcl::NONE;
/**
* Determine if the current user is authorized to perform
* the requested action aganist the API.
*
* @return bool
*
* @throws \Pterodactyl\Exceptions\PterodactylException
*/
public function authorize(): bool
{
if (is_null($this->resource)) {
throw new PterodactylException('An ACL resource must be defined on API requests.');
}
return AdminAcl::check($this->key(), $this->resource, $this->permission);
}
/**
* Determine if the requested resource exists on the server.
*
* @return bool
*/
public function resourceExists(): bool
{
return true;
}
/**
* Default set of rules to apply to API requests.
*
* @return array
*/
public function rules(): array
{
return [];
}
/**
* Return the API key being used for the request.
*
* @return \Pterodactyl\Models\ApiKey
*/
public function key(): ApiKey
{
return $this->attributes->get('api_key');
}
/**
* Grab a model from the route parameters. If no model exists under
* the specified key a default response is returned.
*
* @param string $model
* @param mixed $default
* @return mixed
*/
public function getModel(string $model, $default = null)
{
$parameterKey = array_get(array_flip(ApiSubstituteBindings::getMappings()), $model);
if (! is_null($parameterKey)) {
$model = $this->route()->parameter($parameterKey);
}
return $model ?? $default;
}
/*
* Determine if the request passes the authorization check as well
* as the exists check.
*
* @return bool
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
/**
* @return bool
*/
protected function passesAuthorization()
{
if (! parent::passesAuthorization()) {
return false;
}
// Only let the user know that a resource does not exist if they are
// authenticated to access the endpoint. This avoids exposing that
// an item exists (or does not exist) to the user until they can prove
// that they have permission to know about it.
if ($this->attributes->get('is_missing_model', false) || ! $this->resourceExists()) {
throw new NotFoundHttpException('The requested resource does not exist on this server.');
}
return true;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Locations;
use Pterodactyl\Models\Location;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteLocationRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_LOCATIONS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Determine if the requested location exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$location = $this->route()->parameter('location');
return $location instanceof Location && $location->exists;
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Locations;
use Pterodactyl\Models\Location;
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest;
class GetLocationRequest extends GetLocationsRequest
{
/**
* Determine if the requested location exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$location = $this->route()->parameter('location');
return $location instanceof Location && $location->exists;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Locations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetLocationsRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_LOCATIONS;
/**
* @var int
*/
protected $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,46 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Locations;
use Pterodactyl\Models\Location;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreLocationRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_LOCATIONS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Rules to validate the request aganist.
*
* @return array
*/
public function rules(): array
{
return collect(Location::getCreateRules())->only([
'long',
'short',
])->toArray();
}
/**
* Rename fields to be more clear in error messages.
*
* @return array
*/
public function attributes()
{
return [
'long' => 'Location Description',
'short' => 'Location Identifier',
];
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Locations;
use Pterodactyl\Models\Location;
use Pterodactyl\Http\Controllers\Api\Application\Locations\StoreLocationRequest;
class UpdateLocationRequest extends StoreLocationRequest
{
/**
* Determine if the requested location exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$location = $this->route()->parameter('location');
return $location instanceof Location && $location->exists;
}
/**
* Rules to validate this request aganist.
*
* @return array
*/
public function rules(): array
{
$locationId = $this->route()->parameter('location')->id;
return collect(Location::getUpdateRulesForId($locationId))->only([
'short',
'long',
]);
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetEggRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_EGGS;
/**
* @var int
*/
protected $permission = AdminAcl::READ;
/**
* Determine if the requested egg exists for the selected nest.
*
* @return bool
*/
public function resourceExists(): bool
{
return $this->getModel('nest')->id === $this->getModel('egg')->nest_id;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetEggsRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_EGGS;
/**
* @var int
*/
protected $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,19 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetNestsRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_NESTS;
/**
* @var int
*/
protected $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,33 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Pterodactyl\Models\Node;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteNodeRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_NODES;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Determine if the node being requested for editing exists
* on the Panel before validating the data.
*
* @return bool
*/
public function resourceExists(): bool
{
$node = $this->route()->parameter('node');
return $node instanceof Node && $node->exists;
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Pterodactyl\Models\Node;
class GetNodeRequest extends GetNodesRequest
{
/**
* Determine if the requested node exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$node = $this->route()->parameter('node');
return $node instanceof Node && $node->exists;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetNodesRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_NODES;
/**
* @var int
*/
protected $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,83 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Pterodactyl\Models\Node;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreNodeRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_NODES;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Validation rules to apply to this request.
*
* @param null|array $rules
* @return array
*/
public function rules(array $rules = null): array
{
return collect($rules ?? Node::getCreateRules())->only([
'public',
'name',
'location_id',
'fqdn',
'scheme',
'behind_proxy',
'memory',
'memory_overallocate',
'disk',
'disk_overallocation',
'upload_size',
'daemonListen',
'daemonSFTP',
'daemonBase',
])->mapWithKeys(function ($value, $key) {
$key = ($key === 'daemonSFTP') ? 'daemonSftp' : $key;
return [snake_case($key) => $value];
})->toArray();
}
/**
* Fields to rename for clarity in the API response.
*
* @return array
*/
public function attributes()
{
return [
'daemon_base' => 'Daemon Base Path',
'upload_size' => 'File Upload Size Limit',
'location_id' => 'Location',
'public' => 'Node Visibility',
];
}
/**
* Change the formatting of some data keys in the validated response data
* to match what the application expects in the services.
*
* @return array
*/
public function validated()
{
$response = parent::validated();
$response['daemonListen'] = $response['daemon_listen'];
$response['daemonSFTP'] = $response['daemon_sftp'];
$response['daemonBase'] = $response['daemon_base'];
unset($response['daemon_base'], $response['daemon_listen'], $response['daemon_sftp']);
return $response;
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Pterodactyl\Models\Node;
class UpdateNodeRequest extends StoreNodeRequest
{
/**
* Determine if the node being requested for editing exists
* on the Panel before validating the data.
*
* @return bool
*/
public function resourceExists(): bool
{
$node = $this->route()->parameter('node');
return $node instanceof Node && $node->exists;
}
/**
* Apply validation rules to this request. Uses the parent class rules()
* function but passes in the rules for updating rather than creating.
*
* @param array|null $rules
* @return array
*/
public function rules(array $rules = null): array
{
$nodeId = $this->route()->parameter('node')->id;
return parent::rules(Node::getUpdateRulesForId($nodeId));
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetServerDatabaseRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
/**
* @var int
*/
protected $permission = AdminAcl::READ;
/**
* Determine if the requested server database exists.
*
* @return bool
*/
public function resourceExists(): bool
{
$server = $this->route()->parameter('server');
$database = $this->route()->parameter('database');
return $database->server_id === $server->id;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetServerDatabasesRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
/**
* @var int
*/
protected $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
class ServerDatabaseWriteRequest extends GetServerDatabasesRequest
{
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
}

View file

@ -0,0 +1,61 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreServerDatabaseRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Validation rules for database creation.
*
* @return array
*/
public function rules(): array
{
return [
'database' => 'required|string|min:1|max:24',
'remote' => 'required|string|min:1',
'host' => 'required|integer|exists:database_hosts,id',
];
}
/**
* Return data formatted in the correct format for the service to consume.
*
* @return array
*/
public function validated()
{
return [
'database' => $this->input('database'),
'remote' => $this->input('remote'),
'database_host_id' => $this->input('host'),
];
}
/**
* Format error messages in a more understandable format for API output.
*
* @return array
*/
public function attributes()
{
return [
'host' => 'Database Host Server ID',
'remote' => 'Remote Connection String',
'database' => 'Database Name',
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetServersRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_SERVERS;
/**
* @var int
*/
protected $permission = AdminAcl::READ;
/**
* @return array
*/
public function rules(): array
{
return [
'search' => 'string|max:100',
];
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Models\Server;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class ServerWriteRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_SERVERS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Determine if the requested server exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$server = $this->route()->parameter('server');
return $server instanceof Server && $server->exists;
}
}

View file

@ -0,0 +1,148 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Models\Server;
use Illuminate\Validation\Rule;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Illuminate\Contracts\Validation\Validator;
use Pterodactyl\Models\Objects\DeploymentObject;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreServerRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_SERVERS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Rules to be applied to this request.
*
* @return array
*/
public function rules(): array
{
$rules = Server::getCreateRules();
return [
'name' => $rules['name'],
'description' => array_merge(['nullable'], $rules['description']),
'user' => $rules['owner_id'],
'egg' => $rules['egg_id'],
'pack' => $rules['pack_id'],
'docker_image' => $rules['image'],
'startup' => $rules['startup'],
'environment' => 'required|array',
'skip_scripts' => 'sometimes|boolean',
// Resource limitations
'limits' => 'required|array',
'limits.memory' => $rules['memory'],
'limits.swap' => $rules['swap'],
'limits.disk' => $rules['disk'],
'limits.io' => $rules['io'],
'limits.cpu' => $rules['cpu'],
// Automatic deployment rules
'deploy' => 'sometimes|required|array',
'deploy.locations' => 'array',
'deploy.locations.*' => 'integer|min:1',
'deploy.dedicated_ip' => 'required_with:deploy,boolean',
'deploy.port_range' => 'array',
'deploy.port_range.*' => 'string',
'start_on_completion' => 'sometimes|boolean',
];
}
/**
* Normalize the data into a format that can be consumed by the service.
*
* @return array
*/
public function validated()
{
$data = parent::validated();
return [
'name' => array_get($data, 'name'),
'description' => array_get($data, 'description'),
'owner_id' => array_get($data, 'user'),
'egg_id' => array_get($data, 'egg'),
'pack_id' => array_get($data, 'pack'),
'image' => array_get($data, 'docker_image'),
'startup' => array_get($data, 'startup'),
'environment' => array_get($data, 'environment'),
'memory' => array_get($data, 'limits.memory'),
'swap' => array_get($data, 'limits.swap'),
'disk' => array_get($data, 'limits.disk'),
'io' => array_get($data, 'limits.io'),
'cpu' => array_get($data, 'limits.cpu'),
'skip_scripts' => array_get($data, 'skip_scripts', false),
'allocation_id' => array_get($data, 'allocation.default'),
'allocation_additional' => array_get($data, 'allocation.additional'),
'start_on_completion' => array_get($data, 'start_on_completion', false),
];
}
/*
* Run validation after the rules above have been applied.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
*/
public function withValidator(Validator $validator)
{
$validator->sometimes('allocation.default', [
'required', 'integer', 'bail',
Rule::exists('allocations', 'id')->where(function ($query) {
$query->where('node_id', $this->input('node_id'));
$query->whereNull('server_id');
}),
], function ($input) {
return ! ($input->deploy);
});
$validator->sometimes('allocation.additional.*', [
'integer',
Rule::exists('allocations', 'id')->where(function ($query) {
$query->where('node_id', $this->input('node_id'));
$query->whereNull('server_id');
}),
], function ($input) {
return ! ($input->deploy);
});
$validator->sometimes('deploy.locations', 'present', function ($input) {
return $input->deploy;
});
$validator->sometimes('deploy.port_range', 'present', function ($input) {
return $input->deploy;
});
}
/**
* Return a deployment object that can be passed to the server creation service.
*
* @return \Pterodactyl\Models\Objects\DeploymentObject|null
*/
public function getDeploymentObject()
{
if (is_null($this->input('deploy'))) {
return null;
}
$object = new DeploymentObject;
$object->setDedicated($this->input('deploy.dedicated_ip', false));
$object->setLocations($this->input('deploy.locations', []));
$object->setPorts($this->input('deploy.port_range', []));
return $object;
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Models\Server;
class UpdateServerBuildConfigurationRequest extends ServerWriteRequest
{
/**
* Return the rules to validate this request aganist.
*
* @return array
*/
public function rules(): array
{
$rules = Server::getUpdateRulesForId($this->route()->parameter('server')->id);
return [
'allocation' => $rules['allocation_id'],
'memory' => $rules['memory'],
'swap' => $rules['swap'],
'io' => $rules['io'],
'cpu' => $rules['cpu'],
'disk' => $rules['disk'],
'add_allocations' => 'bail|array',
'add_allocations.*' => 'integer',
'remove_allocations' => 'bail|array',
'remove_allocations.*' => 'integer',
];
}
/**
* Convert the allocation field into the expected format for the service handler.
*
* @return array
*/
public function validated()
{
$data = parent::validated();
$data['allocation_id'] = $data['allocation'];
unset($data['allocation']);
return $data;
}
/**
* Custom attributes to use in error message responses.
*
* @return array
*/
public function attributes()
{
return [
'add_allocations' => 'allocations to add',
'remove_allocations' => 'allocations to remove',
'add_allocations.*' => 'allocation to add',
'remove_allocations.*' => 'allocation to remove',
];
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Models\Server;
class UpdateServerDetailsRequest extends ServerWriteRequest
{
/**
* Rules to apply to a server details update request.
*
* @return array
*/
public function rules(): array
{
$rules = Server::getUpdateRulesForId($this->route()->parameter('server')->id);
return [
'name' => $rules['name'],
'user' => $rules['owner_id'],
'description' => array_merge(['nullable'], $rules['description']),
];
}
/**
* Convert the posted data into the correct format that is expected
* by the application.
*
* @return array
*/
public function validated(): array
{
return [
'name' => $this->input('name'),
'owner_id' => $this->input('user'),
'description' => $this->input('description'),
];
}
/**
* Rename some of the attributes in error messages to clarify the field
* being discussed.
*
* @return array
*/
public function attributes(): array
{
return [
'user' => 'User ID',
'name' => 'Server Name',
];
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Models\Server;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class UpdateServerStartupRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_SERVERS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Validation rules to run the input aganist.
*
* @return array
*/
public function rules(): array
{
$data = Server::getUpdateRulesForId($this->getModel(Server::class)->id);
return [
'startup' => $data['startup'],
'environment' => 'present|array',
'egg' => $data['egg_id'],
'pack' => $data['pack_id'],
'image' => $data['image'],
'skip_scripts' => 'present|boolean',
];
}
/**
* Return the validated data in a format that is expected by the service.
*
* @return array
*/
public function validated()
{
$data = parent::validated();
return collect($data)->only(['startup', 'environment', 'skip_scripts'])->merge([
'egg_id' => array_get($data, 'egg'),
'pack_id' => array_get($data, 'pack'),
'docker_image' => array_get($data, 'image'),
])->toArray();
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Users;
use Pterodactyl\Models\User;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteUserRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_USERS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Determine if the requested user exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$user = $this->route()->parameter('user');
return $user instanceof User && $user->exists;
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Users;
use Pterodactyl\Models\User;
class GetUserRequest extends GetUsersRequest
{
/**
* Determine if the requested user exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$user = $this->route()->parameter('user');
return $user instanceof User && $user->exists;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Users;
use Pterodactyl\Services\Acl\Api\AdminAcl as Acl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetUsersRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = Acl::RESOURCE_USERS;
/**
* @var int
*/
protected $permission = Acl::READ;
}

View file

@ -0,0 +1,54 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Users;
use Pterodactyl\Models\User;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreUserRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_USERS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* Return the validation rules for this request.
*
* @return array
*/
public function rules(): array
{
return collect(User::getCreateRules())->only([
'external_id',
'email',
'username',
'name_first',
'name_last',
'password',
'language',
'root_admin',
])->toArray();
}
/**
* Rename some fields to be more user friendly.
*
* @return array
*/
public function attributes()
{
return [
'external_id' => 'Third Party Identifier',
'name_first' => 'First Name',
'name_last' => 'Last Name',
'root_admin' => 'Root Administrator Status',
];
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Users;
use Pterodactyl\Models\User;
class UpdateUserRequest extends StoreUserRequest
{
/**
* Determine if the requested user exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$user = $this->route()->parameter('user');
return $user instanceof User && $user->exists;
}
/**
* Return the validation rules for this request.
*
* @return array
*/
public function rules(): array
{
$userId = $this->route()->parameter('user')->id;
return collect(User::getUpdateRulesForId($userId))->only([
'external_id',
'email',
'username',
'name_first',
'name_last',
'password',
'language',
'root_admin',
])->toArray();
}
}

View file

@ -1,10 +1,10 @@
<?php <?php
namespace Pterodactyl\Http\Requests\API\Remote; namespace Pterodactyl\Http\Requests\Api\Remote;
use Pterodactyl\Http\Requests\Request; use Illuminate\Foundation\Http\FormRequest;
class SftpAuthenticationFormRequest extends Request class SftpAuthenticationFormRequest extends FormRequest
{ {
/** /**
* Authenticate the request. * Authenticate the request.

View file

@ -0,0 +1,23 @@
<?php
namespace Pterodactyl\Http\Requests\Base;
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
class StoreAccountKeyRequest extends FrontendUserFormRequest
{
/**
* Rules to validate the request input aganist 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,9 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
abstract class Request extends FormRequest
{
}

View file

@ -1,80 +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\Models;
use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class APIKey extends Model implements CleansAttributes, ValidableContract
{
use Eloquence, Validable;
const KEY_LENGTH = 32;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'api_keys';
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'allowed_ips' => 'json',
];
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Rules defining what fields must be passed when making a model.
*
* @var array
*/
protected static $applicationRules = [
'memo' => 'required',
'user_id' => 'required',
'token' => 'required',
];
/**
* Rules to protect aganist invalid data entry to DB.
*
* @var array
*/
protected static $dataIntegrityRules = [
'user_id' => 'exists:users,id',
'token' => 'string|size:32',
'memo' => 'nullable|string|max:500',
'allowed_ips' => 'nullable|json',
'expires_at' => 'nullable|datetime',
];
/**
* Gets the permissions associated with a key.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function permissions()
{
return $this->hasMany(APIPermission::class, 'key_id');
}
}

View file

@ -1,11 +1,4 @@
<?php <?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\Models; namespace Pterodactyl\Models;

View file

@ -1,126 +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\Models;
use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class APIPermission extends Model implements CleansAttributes, ValidableContract
{
use Eloquence, Validable;
/**
* List of permissions available for the API.
*/
const CONST_PERMISSIONS = [
// Items within this block are available to non-adminitrative users.
'_user' => [
'server' => [
'list',
'view',
'power',
'command',
],
],
// All other pemissions below are administrative actions.
'server' => [
'list',
'create',
'view',
'edit-details',
'edit-container',
'edit-build',
'edit-startup',
'suspend',
'install',
'rebuild',
'delete',
],
'location' => [
'list',
],
'node' => [
'list',
'view',
'view-config',
'create',
'delete',
],
'user' => [
'list',
'view',
'create',
'edit',
'delete',
],
'service' => [
'list',
'view',
],
'option' => [
'list',
'view',
],
'pack' => [
'list',
'view',
],
];
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'api_permissions';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id'];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'key_id' => 'integer',
];
protected static $dataIntegrityRules = [
'key_id' => 'required|numeric',
'permission' => 'required|string|max:200',
];
/**
* Disable timestamps for this table.
*
* @var bool
*/
public $timestamps = false;
/**
* Return permissions for API.
*
* @return array
* @deprecated
*/
public static function permissions()
{
return [];
}
}

View file

@ -1,11 +1,4 @@
<?php <?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\Models; namespace Pterodactyl\Models;
@ -19,6 +12,12 @@ class Allocation extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;
/**
* The resource name for this model when it is transformed into an
* API representation using fractal.
*/
const RESOURCE_NAME = 'allocation';
/** /**
* The table associated with the model. * The table associated with the model.
* *
@ -105,4 +104,14 @@ class Allocation extends Model implements CleansAttributes, ValidableContract
{ {
return $this->belongsTo(Server::class); return $this->belongsTo(Server::class);
} }
/**
* Return the Node model associated with this allocation.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function node()
{
return $this->belongsTo(Node::class);
}
} }

130
app/Models/ApiKey.php Normal file
View file

@ -0,0 +1,130 @@
<?php
namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class ApiKey extends Model implements CleansAttributes, ValidableContract
{
use Eloquence, Validable;
/**
* Different API keys that can exist on the system.
*/
const TYPE_NONE = 0;
const TYPE_ACCOUNT = 1;
const TYPE_APPLICATION = 2;
const TYPE_DAEMON_USER = 3;
const TYPE_DAEMON_APPLICATION = 4;
/**
* The length of API key identifiers.
*/
const IDENTIFIER_LENGTH = 16;
/**
* The length of the actual API key that is encrypted and stored
* in the database.
*/
const KEY_LENGTH = 32;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'api_keys';
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'allowed_ips' => 'json',
'user_id' => 'int',
'r_' . AdminAcl::RESOURCE_USERS => 'int',
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int',
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int',
'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'int',
'r_' . AdminAcl::RESOURCE_EGGS => 'int',
'r_' . AdminAcl::RESOURCE_LOCATIONS => 'int',
'r_' . AdminAcl::RESOURCE_NESTS => 'int',
'r_' . AdminAcl::RESOURCE_NODES => 'int',
'r_' . AdminAcl::RESOURCE_PACKS => 'int',
'r_' . AdminAcl::RESOURCE_SERVERS => 'int',
];
/**
* Fields that are mass assignable.
*
* @var array
*/
protected $fillable = [
'identifier',
'token',
'allowed_ips',
'memo',
'last_used_at',
];
/**
* Fields that should not be included when calling toArray() or toJson()
* on this model.
*
* @var array
*/
protected $hidden = ['token'];
/**
* Rules defining what fields must be passed when making a model.
*
* @var array
*/
protected static $applicationRules = [
'identifier' => 'required',
'memo' => 'required',
'user_id' => 'required',
'token' => 'required',
'key_type' => 'present',
];
/**
* Rules to protect aganist invalid data entry to DB.
*
* @var array
*/
protected static $dataIntegrityRules = [
'user_id' => 'exists:users,id',
'key_type' => 'integer|min:0|max:4',
'identifier' => 'string|size:16|unique:api_keys,identifier',
'token' => 'string',
'memo' => 'nullable|string|max:500',
'allowed_ips' => 'nullable|json',
'last_used_at' => 'nullable|date',
'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_EGGS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_LOCATIONS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_NESTS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_NODES => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_PACKS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3',
];
/**
* @var array
*/
protected $dates = [
self::CREATED_AT,
self::UPDATED_AT,
'last_used_at',
];
}

View file

@ -1,38 +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\Models;
use Illuminate\Database\Eloquent\Model;
class Checksum extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'checksums';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'service' => 'integer',
];
}

View file

@ -1,26 +1,4 @@
<?php <?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\Models; namespace Pterodactyl\Models;

View file

@ -1,11 +1,4 @@
<?php <?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\Models; namespace Pterodactyl\Models;
@ -19,6 +12,12 @@ class Database extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;
/**
* The resource name for this model when it is transformed into an
* API representation using fractal.
*/
const RESOURCE_NAME = 'server_database';
/** /**
* The table associated with the model. * The table associated with the model.
* *

View file

@ -1,11 +1,4 @@
<?php <?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\Models; namespace Pterodactyl\Models;
@ -19,6 +12,12 @@ class DatabaseHost extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;
/**
* The resource name for this model when it is transformed into an
* API representation using fractal.
*/
const RESOURCE_NAME = 'database_host';
/** /**
* The table associated with the model. * The table associated with the model.
* *

View file

@ -1,11 +1,4 @@
<?php <?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\Models; namespace Pterodactyl\Models;
@ -19,6 +12,12 @@ class Egg extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;
/**
* The resource name for this model when it is transformed into an
* API representation using fractal.
*/
const RESOURCE_NAME = 'egg';
/** /**
* The table associated with the model. * The table associated with the model.
* *

View file

@ -1,11 +1,4 @@
<?php <?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\Models; namespace Pterodactyl\Models;
@ -19,6 +12,12 @@ class EggVariable extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;
/**
* The resource name for this model when it is transformed into an
* API representation using fractal.
*/
const RESOURCE_NAME = 'egg_variable';
/** /**
* Reserved environment variable names. * Reserved environment variable names.
* *

View file

@ -1,11 +1,4 @@
<?php <?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\Models; namespace Pterodactyl\Models;
@ -19,6 +12,12 @@ class Location extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use Eloquence, Validable;
/**
* The resource name for this model when it is transformed into an
* API representation using fractal.
*/
const RESOURCE_NAME = 'location';
/** /**
* The table associated with the model. * The table associated with the model.
* *

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