Merge branch 'develop' into dane/restore-backups

This commit is contained in:
Dane Everitt 2021-01-25 19:16:40 -08:00
commit 663143de0b
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
575 changed files with 6080 additions and 6864 deletions

View file

@ -14,8 +14,6 @@ class ServerInstalled
/**
* Checks that the server is installed before allowing access through the route.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
@ -23,10 +21,8 @@ class ServerInstalled
/** @var \Pterodactyl\Models\Server|null $server */
$server = $request->route()->parameter('server');
if (! $server instanceof Server) {
throw new NotFoundHttpException(
'No server resource was located in the request parameters.'
);
if (!$server instanceof Server) {
throw new NotFoundHttpException('No server resource was located in the request parameters.');
}
if (! $server->isInstalled()) {

View file

@ -18,16 +18,14 @@ class AdminAuthenticate
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function handle(Request $request, Closure $next)
{
if (! $request->user() || ! $request->user()->root_admin) {
throw new AccessDeniedHttpException;
if (!$request->user() || !$request->user()->root_admin) {
throw new AccessDeniedHttpException();
}
return $next($request);

View file

@ -42,7 +42,7 @@ class ApiSubstituteBindings extends SubstituteBindings
* 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)
@ -50,7 +50,7 @@ class ApiSubstituteBindings extends SubstituteBindings
$route = $request->route();
foreach (self::$mappings as $key => $model) {
if (! is_null($this->router->getBindingCallback($key))) {
if (!is_null($this->router->getBindingCallback($key))) {
continue;
}

View file

@ -12,13 +12,11 @@ class AuthenticateApplicationUser
* Authenticate that the currently authenticated user is an administrator
* and should be allowed to proceed 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) {
if (is_null($request->user()) || !$request->user()->root_admin) {
throw new AccessDeniedHttpException('This account does not have permission to access the API.');
}

View file

@ -13,8 +13,6 @@ class AuthenticateIPAccess
/**
* Determine if a request IP has permission to access the API.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Exception

View file

@ -3,7 +3,7 @@
namespace Pterodactyl\Http\Middleware\Api;
use Closure;
use Cake\Chronos\Chronos;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Pterodactyl\Models\ApiKey;
@ -33,10 +33,6 @@ class AuthenticateKey
/**
* AuthenticateKey constructor.
*
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
* @param \Illuminate\Auth\AuthManager $auth
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
*/
public function __construct(ApiKeyRepositoryInterface $repository, AuthManager $auth, Encrypter $encrypter)
{
@ -49,9 +45,6 @@ class AuthenticateKey
* Handle an API request by verifying that the provided API key
* is in a valid format and exists in the database.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param int $keyType
* @return mixed
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
@ -85,10 +78,6 @@ class AuthenticateKey
/**
* Authenticate an API key.
*
* @param string $key
* @param int $keyType
* @return \Pterodactyl\Models\ApiKey
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
@ -103,14 +92,14 @@ class AuthenticateKey
['key_type', '=', $keyType],
]);
} catch (RecordNotFoundException $exception) {
throw new AccessDeniedHttpException;
throw new AccessDeniedHttpException();
}
if (! hash_equals($this->encrypter->decrypt($model->token), $token)) {
throw new AccessDeniedHttpException;
if (!hash_equals($this->encrypter->decrypt($model->token), $token)) {
throw new AccessDeniedHttpException();
}
$this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => Chronos::now()]);
$this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => CarbonImmutable::now()]);
return $model;
}

View file

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

View file

@ -8,8 +8,8 @@ use Pterodactyl\Models\Server;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Pterodactyl\Exceptions\Http\Server\ServerTransferringException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class AuthenticateServerAccess
{
@ -29,8 +29,6 @@ class AuthenticateServerAccess
/**
* AuthenticateServerAccess constructor.
*
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(ServerRepositoryInterface $repository)
{
@ -40,8 +38,6 @@ class AuthenticateServerAccess
/**
* Authenticate that this server exists and is not suspended or marked as installing.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
@ -50,39 +46,37 @@ class AuthenticateServerAccess
$user = $request->user();
$server = $request->route()->parameter('server');
if (! $server instanceof Server) {
if (!$server instanceof Server) {
throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
}
// At the very least, ensure that the user trying to make this request is the
// server owner, a subuser, or a root admin. We'll leave it up to the controllers
// to authenticate more detailed permissions if needed.
if ($user->id !== $server->owner_id && ! $user->root_admin) {
if ($user->id !== $server->owner_id && !$user->root_admin) {
// Check for subuser status.
if (! $server->subusers->contains('user_id', $user->id)) {
if (!$server->subusers->contains('user_id', $user->id)) {
throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
}
}
if ($server->isSuspended() && ! $request->routeIs('api:client:server.resources')) {
throw new BadRequestHttpException(
'This server is currently suspended and the functionality requested is unavailable.'
);
if ($server->suspended && !$request->routeIs('api:client:server.resources')) {
throw new BadRequestHttpException('This server is currently suspended and the functionality requested is unavailable.');
}
// Still allow users to get information about their server if it is installing or being transferred.
if (! $request->routeIs('api:client:server.view')) {
if (! $server->isInstalled()) {
if (!$request->routeIs('api:client:server.view')) {
if (!$server->isInstalled()) {
// Throw an exception for all server routes; however if the user is an admin and requesting the
// server details, don't throw the exception for them.
if (! $user->root_admin || ($user->root_admin && ! $request->routeIs($this->except))) {
if (!$user->root_admin || ($user->root_admin && !$request->routeIs($this->except))) {
throw new ConflictHttpException('Server has not completed the installation process.');
}
}
if (! is_null($server->transfer)) {
if (! $user->root_admin || ($user->root_admin && ! $request->routeIs($this->except))) {
throw new ServerTransferringException;
if (!is_null($server->transfer)) {
if (!$user->root_admin || ($user->root_admin && !$request->routeIs($this->except))) {
throw new ServerTransferringException();
}
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Pterodactyl\Http\Middleware\Api\Client\Server;
use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Models\Task;
use Pterodactyl\Models\User;
use InvalidArgumentException;
use Pterodactyl\Models\Backup;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Subuser;
use Pterodactyl\Models\Database;
use Pterodactyl\Models\Schedule;
use Pterodactyl\Models\Allocation;
use Illuminate\Database\Eloquent\Model;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ResourceBelongsToServer
{
/**
* Looks at the request parameters to determine if the given resource belongs
* to the requested server. If not, a 404 error will be returned to the caller.
*
* This is critical to ensuring that all subsequent logic is using exactly the
* server that is expected, and that we're not accessing a resource completely
* unrelated to the server provided in the request.
*
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$params = $request->route()->parameters();
if (is_null($params) || !$params['server'] instanceof Server) {
throw new InvalidArgumentException('This middleware cannot be used in a context that is missing a server in the parameters.');
}
/** @var \Pterodactyl\Models\Server $server */
$server = $request->route()->parameter('server');
$exception = new NotFoundHttpException('The requested resource was not found for this server.');
foreach ($params as $key => $model) {
// Specifically skip the server, we're just trying to see if all of the
// other resources are assigned to this server. Also skip anything that
// is not currently a Model instance since those will just end up being
// a 404 down the road.
if ($key === 'server' || !$model instanceof Model) {
continue;
}
switch (get_class($model)) {
// All of these models use "server_id" as the field key for the server
// they are assigned to, so the logic is identical for them all.
case Allocation::class:
case Backup::class:
case Database::class:
case Schedule::class:
case Subuser::class:
if ($model->server_id !== $server->id) {
throw $exception;
}
break;
// Regular users are a special case here as we need to make sure they're
// currently assigned as a subuser on the server.
case User::class:
$subuser = $server->subusers()->where('user_id', $model->id)->first();
if (is_null($subuser)) {
throw $exception;
}
// This is a special case to avoid an additional query being triggered
// in the underlying logic.
$request->attributes->set('subuser', $subuser);
break;
// Tasks are special since they're (currently) the only item in the API
// that requires something in addition to the server in order to be accessed.
case Task::class:
$schedule = $request->route()->parameter('schedule');
if ($model->schedule_id !== $schedule->id || $schedule->server_id !== $server->id) {
throw $exception;
}
break;
default:
// Don't return a 404 here since we want to make sure no one relies
// on this middleware in a context in which it will not work. Fail safe.
throw new InvalidArgumentException('There is no handler configured for a resource of this type: ' . get_class($model));
}
}
return $next($request);
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace Pterodactyl\Http\Middleware\Api\Client\Server;
use Closure;
use Illuminate\Http\Request;
class SubuserBelongsToServer
{
/**
* Ensure that the user being accessed in the request is a user that is currently assigned
* as a subuser for this server instance. We'll let the requests themselves handle wether or
* not the user making the request can actually modify or delete the subuser record.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
/** @var \Pterodactyl\Models\Server $server */
$server = $request->route()->parameter('server');
/** @var \Pterodactyl\Models\User $user */
$user = $request->route()->parameter('user');
// Don't do anything if there isn't a user present in the request.
if (is_null($user)) {
return $next($request);
}
$request->attributes->set('subuser', $server->subusers()->where('user_id', $user->id)->firstOrFail());
return $next($request);
}
}

View file

@ -19,7 +19,7 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings
* 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)
@ -43,7 +43,7 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings
}
});
$this->router->bind('database', function ($value) use ($request) {
$this->router->bind('database', function ($value) {
$id = Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value);
return Database::query()->where('id', $id)->firstOrFail();

View file

@ -34,9 +34,6 @@ class DaemonAuthenticate
/**
* DaemonAuthenticate constructor.
*
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $repository
*/
public function __construct(Encrypter $encrypter, NodeRepository $repository)
{
@ -47,8 +44,6 @@ class DaemonAuthenticate
/**
* Check if a request from the daemon can be properly attributed back to a single node instance.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
@ -60,17 +55,13 @@ class DaemonAuthenticate
}
if (is_null($bearer = $request->bearerToken())) {
throw new HttpException(
401, 'Access this this endpoint must include an Authorization header.', null, ['WWW-Authenticate' => 'Bearer']
);
throw new HttpException(401, 'Access this this endpoint must include an Authorization header.', null, ['WWW-Authenticate' => 'Bearer']);
}
$parts = explode('.', $bearer);
// Ensure that all of the correct parts are provided in the header.
if (count($parts) !== 2 || empty($parts[0]) || empty($parts[1])) {
throw new BadRequestHttpException(
'The Authorization header provided was not in a valid format.'
);
throw new BadRequestHttpException('The Authorization header provided was not in a valid format.');
}
try {
@ -88,8 +79,6 @@ class DaemonAuthenticate
// Do nothing, we don't want to expose a node not existing at all.
}
throw new AccessDeniedHttpException(
'You are not authorized to access this resource.'
);
throw new AccessDeniedHttpException('You are not authorized to access this resource.');
}
}

View file

@ -13,23 +13,15 @@ class IsValidJson
* parsing the data. This avoids confusing validation errors where every field is flagged and
* it is not immediately clear that there is an issue with the JSON being passed.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if ($request->isJson() && ! empty($request->getContent())) {
if ($request->isJson() && !empty($request->getContent())) {
json_decode($request->getContent(), true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new BadRequestHttpException(
sprintf(
'The JSON data passed in the request appears to be malformed. err_code: %d err_message: "%s"',
json_last_error(),
json_last_error_msg()
)
);
throw new BadRequestHttpException(sprintf('The JSON data passed in the request appears to be malformed. err_code: %d err_message: "%s"', json_last_error(), json_last_error_msg()));
}
}

View file

@ -15,8 +15,6 @@ class SetSessionDriver
/**
* SetSessionDriver constructor.
*
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(ConfigRepository $config)
{
@ -26,8 +24,6 @@ class SetSessionDriver
/**
* Set the session for API calls to only last for the one request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)

View file

@ -11,16 +11,14 @@ class Authenticate
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function handle(Request $request, Closure $next)
{
if (! $request->user()) {
throw new AuthenticationException;
if (!$request->user()) {
throw new AuthenticationException();
}
return $next($request);

View file

@ -15,8 +15,6 @@ class LanguageMiddleware
/**
* LanguageMiddleware constructor.
*
* @param \Illuminate\Foundation\Application $app
*/
public function __construct(Application $app)
{
@ -26,8 +24,6 @@ class LanguageMiddleware
/**
* Handle an incoming request and set the user's preferred language.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)

View file

@ -14,8 +14,6 @@ class MaintenanceMiddleware
/**
* MaintenanceMiddleware constructor.
*
* @param \Illuminate\Contracts\Routing\ResponseFactory $response
*/
public function __construct(ResponseFactory $response)
{
@ -26,7 +24,7 @@ class MaintenanceMiddleware
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)

View file

@ -15,8 +15,6 @@ class RedirectIfAuthenticated
/**
* RedirectIfAuthenticated constructor.
*
* @param \Illuminate\Auth\AuthManager $authManager
*/
public function __construct(AuthManager $authManager)
{
@ -26,9 +24,6 @@ class RedirectIfAuthenticated
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle(Request $request, Closure $next, string $guard = null)

View file

@ -10,9 +10,9 @@ use Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException;
class RequireTwoFactorAuthentication
{
const LEVEL_NONE = 0;
const LEVEL_ADMIN = 1;
const LEVEL_ALL = 2;
public const LEVEL_NONE = 0;
public const LEVEL_ADMIN = 1;
public const LEVEL_ALL = 2;
/**
* @var \Prologue\Alerts\AlertsMessageBag
@ -28,8 +28,6 @@ class RequireTwoFactorAuthentication
/**
* RequireTwoFactorAuthentication constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
*/
public function __construct(AlertsMessageBag $alert)
{
@ -42,8 +40,6 @@ class RequireTwoFactorAuthentication
* order to perform actions. If so, we check the level at which it is required (all users
* or just admins) and then check if the user has enabled it for their account.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException
@ -55,24 +51,24 @@ class RequireTwoFactorAuthentication
$uri = rtrim($request->getRequestUri(), '/') . '/';
$current = $request->route()->getName();
if (! $user || Str::startsWith($uri, ['/auth/']) || Str::startsWith($current, ['auth.', 'account.'])) {
if (!$user || Str::startsWith($uri, ['/auth/']) || Str::startsWith($current, ['auth.', 'account.'])) {
return $next($request);
}
$level = (int)config('pterodactyl.auth.2fa_required');
$level = (int) config('pterodactyl.auth.2fa_required');
// If this setting is not configured, or the user is already using 2FA then we can just
// send them right through, nothing else needs to be checked.
//
// If the level is set as admin and the user is not an admin, pass them through as well.
if ($level === self::LEVEL_NONE || $user->use_totp) {
return $next($request);
} else if ($level === self::LEVEL_ADMIN && ! $user->root_admin) {
} elseif ($level === self::LEVEL_ADMIN && !$user->root_admin) {
return $next($request);
}
// For API calls return an exception which gets rendered nicely in the API response.
if ($request->isJson() || Str::startsWith($uri, '/api/')) {
throw new TwoFactorAuthRequiredException;
throw new TwoFactorAuthRequiredException();
}
$this->alert->danger(trans('auth.2fa_must_be_enabled'))->flash();

View file

@ -31,10 +31,6 @@ class AccessingValidServer
/**
* AccessingValidServer constructor.
*
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Illuminate\Contracts\Routing\ResponseFactory $response
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(
ConfigRepository $config,
@ -49,8 +45,6 @@ class AccessingValidServer
/**
* Determine if a given user has permission to access a server.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response|mixed
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
@ -81,9 +75,9 @@ class AccessingValidServer
return $this->response->view('errors.installing', [], 409);
}
if (! is_null($server->transfer)) {
if (!is_null($server->transfer)) {
if ($isApiRequest) {
throw new ServerTransferringException;
throw new ServerTransferringException();
}
return $this->response->view('errors.transferring', [], 409);

View file

@ -26,9 +26,6 @@ class VerifyReCaptcha
/**
* VerifyReCaptcha constructor.
*
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(Dispatcher $dispatcher, Repository $config)
{
@ -40,12 +37,12 @@ class VerifyReCaptcha
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return \Illuminate\Http\RedirectResponse|mixed
*/
public function handle($request, Closure $next)
{
if (! $this->config->get('recaptcha.enabled')) {
if (!$this->config->get('recaptcha.enabled')) {
return $next($request);
}
@ -61,7 +58,7 @@ class VerifyReCaptcha
if ($res->getStatusCode() === 200) {
$result = json_decode($res->getBody());
if ($result->success && (! $this->config->get('recaptcha.verify_domain') || $this->isResponseVerified($result, $request))) {
if ($result->success && (!$this->config->get('recaptcha.verify_domain') || $this->isResponseVerified($result, $request))) {
return $next($request);
}
}
@ -69,25 +66,20 @@ class VerifyReCaptcha
$this->dispatcher->dispatch(
new FailedCaptcha(
$request->ip(), ! empty($result) ? ($result->hostname ?? null) : null
$request->ip(),
!empty($result) ? ($result->hostname ?? null) : null
)
);
throw new HttpException(
Response::HTTP_BAD_REQUEST, 'Failed to validate reCAPTCHA data.'
);
throw new HttpException(Response::HTTP_BAD_REQUEST, 'Failed to validate reCAPTCHA data.');
}
/**
* Determine if the response from the recaptcha servers was valid.
*
* @param stdClass $result
* @param \Illuminate\Http\Request $request
* @return bool
*/
private function isResponseVerified(stdClass $result, Request $request): bool
{
if (! $this->config->get('recaptcha.verify_domain')) {
if (!$this->config->get('recaptcha.verify_domain')) {
return false;
}