Merge branch 'develop' into feature/server-mounts

This commit is contained in:
Matthew Penner 2020-07-04 15:20:01 -06:00 committed by GitHub
commit 29876e023b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
166 changed files with 5482 additions and 4130 deletions

View file

@ -17,10 +17,12 @@ use Prologue\Alerts\AlertsMessageBag;
use GuzzleHttp\Exception\RequestException;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Validation\ValidationException;
use Pterodactyl\Services\Servers\SuspensionService;
use Pterodactyl\Repositories\Eloquent\MountRepository;
use Pterodactyl\Services\Servers\ServerDeletionService;
use Pterodactyl\Services\Servers\ReinstallServerService;
use Pterodactyl\Exceptions\Model\DataValidationException;
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Services\Servers\BuildModificationService;
use Pterodactyl\Services\Databases\DatabasePasswordService;
@ -284,16 +286,21 @@ class ServersController extends Controller
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Illuminate\Validation\ValidationException
*/
public function updateBuild(Request $request, Server $server)
{
$this->buildModificationService->handle($server, $request->only([
'allocation_id', 'add_allocations', 'remove_allocations',
'memory', 'swap', 'io', 'cpu', 'threads', 'disk',
'database_limit', 'allocation_limit', 'backup_limit', 'oom_disabled',
]));
try {
$this->buildModificationService->handle($server, $request->only([
'allocation_id', 'add_allocations', 'remove_allocations',
'memory', 'swap', 'io', 'cpu', 'threads', 'disk',
'database_limit', 'allocation_limit', 'backup_limit', 'oom_disabled',
]));
} catch (DataValidationException $exception) {
throw new ValidationException($exception->validator);
}
$this->alert->success(trans('admin/server.alerts.build_updated'))->flash();
return redirect()->route('admin.servers.view.build', $server->id);
@ -341,12 +348,12 @@ class ServersController extends Controller
* Creates a new database assigned to a specific server.
*
* @param \Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest $request
* @param int $server
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Exception
* @throws \Throwable
*/
public function newDatabase(StoreServerDatabaseRequest $request, $server)
public function newDatabase(StoreServerDatabaseRequest $request, Server $server)
{
$this->databaseManagementService->create($server, [
'database' => $request->input('database'),
@ -355,7 +362,7 @@ class ServersController extends Controller
'max_connections' => $request->input('max_connections'),
]);
return redirect()->route('admin.servers.view.database', $server)->withInput();
return redirect()->route('admin.servers.view.database', $server->id)->withInput();
}
/**

View file

@ -57,13 +57,12 @@ class DatabaseController extends ApplicationApiController
* server.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request
* @param \Pterodactyl\Models\Server $server
* @return array
*/
public function index(GetServerDatabasesRequest $request): array
public function index(GetServerDatabasesRequest $request, Server $server): array
{
$databases = $this->repository->getDatabasesForServer($request->getModel(Server::class)->id);
return $this->fractal->collection($databases)
return $this->fractal->collection($server->databases)
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
->toArray();
}
@ -72,11 +71,13 @@ class DatabaseController extends ApplicationApiController
* Return a single server database.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest $request
* @param \Pterodactyl\Models\Server $server
* @param \Pterodactyl\Models\Database $database
* @return array
*/
public function view(GetServerDatabaseRequest $request): array
public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array
{
return $this->fractal->item($request->getModel(Database::class))
return $this->fractal->item($database)
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
->toArray();
}
@ -85,29 +86,31 @@ class DatabaseController extends ApplicationApiController
* Reset the password for a specific server database.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request
* @return \Illuminate\Http\Response
* @param \Pterodactyl\Models\Server $server
* @param \Pterodactyl\Models\Database $database
* @return \Illuminate\Http\JsonResponse
*
* @throws \Throwable
*/
public function resetPassword(ServerDatabaseWriteRequest $request): Response
public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse
{
$this->databasePasswordService->handle($request->getModel(Database::class));
$this->databasePasswordService->handle($database);
return response('', 204);
return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT);
}
/**
* Create a new database on the Panel for a given server.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest $request
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\JsonResponse
*
* @throws \Exception
* @throws \Throwable
*/
public function store(StoreServerDatabaseRequest $request): JsonResponse
public function store(StoreServerDatabaseRequest $request, Server $server): JsonResponse
{
$server = $request->getModel(Server::class);
$database = $this->databaseManagementService->create($server->id, $request->validated());
$database = $this->databaseManagementService->create($server, $request->validated());
return $this->fractal->item($database)
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
@ -117,7 +120,7 @@ class DatabaseController extends ApplicationApiController
'database' => $database->id,
]),
])
->respond(201);
->respond(Response::HTTP_CREATED);
}
/**

View file

@ -100,39 +100,20 @@ class UserController extends ApplicationApiController
* meta. If there are no errors this is an empty array.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Users\UpdateUserRequest $request
* @param \Pterodactyl\Models\User $user
* @return array
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(UpdateUserRequest $request): array
public function update(UpdateUserRequest $request, User $user): array
{
$this->updateService->setUserLevel(User::USER_LEVEL_ADMIN);
$collection = $this->updateService->handle($request->getModel(User::class), $request->validated());
$user = $this->updateService->handle($user, $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'))
$response = $this->fractal->item($user)
->transformWith($this->getTransformer(UserTransformer::class));
if (count($errors) > 0) {
$response->addMeta([
'revocation_errors' => $errors,
]);
}
return $response->toArray();
}

View file

@ -52,16 +52,16 @@ class AccountController extends ClientApiController
* Update the authenticated user's email address.
*
* @param \Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest $request
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function updateEmail(UpdateEmailRequest $request): Response
public function updateEmail(UpdateEmailRequest $request): JsonResponse
{
$this->updateService->handle($request->user(), $request->validated());
return response('', Response::HTTP_CREATED);
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
/**
@ -74,12 +74,12 @@ class AccountController extends ClientApiController
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function updatePassword(UpdatePasswordRequest $request): \Illuminate\Http\JsonResponse
public function updatePassword(UpdatePasswordRequest $request): JsonResponse
{
$this->updateService->handle($request->user(), $request->validated());
$this->sessionGuard->logoutOtherDevices($request->input('password'));
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
}

View file

@ -103,6 +103,7 @@ class ApiKeyController extends ClientApiController
public function delete(ClientApiRequest $request, string $identifier)
{
$response = $this->repository->deleteWhere([
'key_type' => ApiKey::TYPE_ACCOUNT,
'user_id' => $request->user()->id,
'identifier' => $identifier,
]);

View file

@ -69,9 +69,7 @@ class DatabaseController extends ClientApiController
*/
public function index(GetDatabasesRequest $request, Server $server): array
{
$databases = $this->repository->getDatabasesForServer($server->id);
return $this->fractal->collection($databases)
return $this->fractal->collection($server->databases)
->transformWith($this->getTransformer(DatabaseTransformer::class))
->toArray();
}
@ -83,6 +81,8 @@ class DatabaseController extends ClientApiController
* @param \Pterodactyl\Models\Server $server
* @return array
*
* @throws \Throwable
* @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException
* @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
*/
public function store(StoreDatabaseRequest $request, Server $server): array

View file

@ -147,7 +147,7 @@ class ScheduleController extends ClientApiController
{
$this->repository->delete($schedule->id);
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
/**

View file

@ -13,6 +13,7 @@ use Pterodactyl\Exceptions\Http\HttpForbiddenException;
use Pterodactyl\Transformers\Api\Client\TaskTransformer;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Exceptions\Service\ServiceLimitExceededException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreTaskRequest;
@ -44,11 +45,15 @@ class ScheduleTaskController extends ClientApiController
* @return array
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\ServiceLimitExceededException
*/
public function store(StoreTaskRequest $request, Server $server, Schedule $schedule)
{
if ($schedule->server_id !== $server->id) {
throw new NotFoundHttpException;
$limit = config('pterodactyl.client_features.schedules.per_schedule_task_limit', 10);
if ($schedule->tasks()->count() >= $limit) {
throw new ServiceLimitExceededException(
"Schedules may not have more than {$limit} tasks associated with them. Creating this task would put this schedule over the limit."
);
}
$lastTask = $schedule->tasks->last();
@ -58,7 +63,7 @@ class ScheduleTaskController extends ClientApiController
'schedule_id' => $schedule->id,
'sequence_id' => ($lastTask->sequence_id ?? 0) + 1,
'action' => $request->input('action'),
'payload' => $request->input('payload'),
'payload' => $request->input('payload') ?? '',
'time_offset' => $request->input('time_offset'),
]);
@ -87,7 +92,7 @@ class ScheduleTaskController extends ClientApiController
$this->repository->update($task->id, [
'action' => $request->input('action'),
'payload' => $request->input('payload'),
'payload' => $request->input('payload') ?? '',
'time_offset' => $request->input('time_offset'),
]);

View file

@ -55,7 +55,7 @@ class SettingsController extends ClientApiController
'name' => $request->input('name'),
]);
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
/**
@ -71,6 +71,6 @@ class SettingsController extends ClientApiController
{
$this->reinstallServerService->reinstall($server);
return JsonResponse::create([], Response::HTTP_ACCEPTED);
return new JsonResponse([], Response::HTTP_ACCEPTED);
}
}

View file

@ -7,7 +7,6 @@ use Illuminate\Http\Response;
use Pterodactyl\Models\Server;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Models\Permission;
use Illuminate\Contracts\Cache\Repository;
use Pterodactyl\Services\Nodes\NodeJWTService;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
@ -16,11 +15,6 @@ use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
class WebsocketController extends ClientApiController
{
/**
* @var \Illuminate\Contracts\Cache\Repository
*/
private $cache;
/**
* @var \Pterodactyl\Services\Nodes\NodeJWTService
*/
@ -36,16 +30,13 @@ class WebsocketController extends ClientApiController
*
* @param \Pterodactyl\Services\Nodes\NodeJWTService $jwtService
* @param \Pterodactyl\Services\Servers\GetUserPermissionsService $permissionsService
* @param \Illuminate\Contracts\Cache\Repository $cache
*/
public function __construct(
NodeJWTService $jwtService,
GetUserPermissionsService $permissionsService,
Repository $cache
GetUserPermissionsService $permissionsService
) {
parent::__construct();
$this->cache = $cache;
$this->jwtService = $jwtService;
$this->permissionsService = $permissionsService;
}
@ -78,7 +69,7 @@ class WebsocketController extends ClientApiController
$socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $server->node->getConnectionAddress());
return JsonResponse::create([
return new JsonResponse([
'data' => [
'token' => $token->__toString(),
'socket' => $socket . sprintf('/api/servers/%s/ws', $server->uuid),

View file

@ -61,11 +61,11 @@ class TwoFactorController extends ClientApiController
*/
public function index(Request $request)
{
if ($request->user()->totp_enabled) {
if ($request->user()->use_totp) {
throw new BadRequestHttpException('Two-factor authentication is already enabled on this account.');
}
return JsonResponse::create([
return new JsonResponse([
'data' => [
'image_url_data' => $this->setupService->handle($request->user()),
],
@ -96,9 +96,14 @@ class TwoFactorController extends ClientApiController
throw new ValidationException($validator);
}
$this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true);
$tokens = $this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true);
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
return new JsonResponse([
'object' => 'recovery_tokens',
'attributes' => [
'tokens' => $tokens,
],
]);
}
/**
@ -124,6 +129,6 @@ class TwoFactorController extends ClientApiController
'use_totp' => false,
]);
return JsonResponse::create([], Response::HTTP_NO_CONTENT);
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
}

View file

@ -68,10 +68,11 @@ abstract class AbstractLoginController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param string|null $message
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null)
protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null, string $message = null)
{
$this->incrementLoginAttempts($request);
$this->fireFailedLoginEvent($user, [
@ -79,7 +80,9 @@ abstract class AbstractLoginController extends Controller
]);
if ($request->route()->named('auth.login-checkpoint')) {
throw new DisplayException(trans('auth.two_factor.checkpoint_failed'));
throw new DisplayException(
$message ?? trans('auth.two_factor.checkpoint_failed')
);
}
throw new DisplayException(trans('auth.failed'));
@ -116,7 +119,7 @@ abstract class AbstractLoginController extends Controller
*/
protected function getField(string $input = null): string
{
return str_contains($input, '@') ? 'email' : 'username';
return ($input && str_contains($input, '@')) ? 'email' : 'username';
}
/**

View file

@ -11,6 +11,7 @@ use Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest;
use Illuminate\Contracts\Cache\Repository as CacheRepository;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository;
class LoginCheckpointController extends AbstractLoginController
{
@ -34,6 +35,11 @@ class LoginCheckpointController extends AbstractLoginController
*/
private $encrypter;
/**
* @var \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository
*/
private $recoveryTokenRepository;
/**
* LoginCheckpointController constructor.
*
@ -42,6 +48,7 @@ class LoginCheckpointController extends AbstractLoginController
* @param \PragmaRX\Google2FA\Google2FA $google2FA
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Illuminate\Contracts\Cache\Repository $cache
* @param \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository $recoveryTokenRepository
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
*/
public function __construct(
@ -50,6 +57,7 @@ class LoginCheckpointController extends AbstractLoginController
Google2FA $google2FA,
Repository $config,
CacheRepository $cache,
RecoveryTokenRepository $recoveryTokenRepository,
UserRepositoryInterface $repository
) {
parent::__construct($auth, $config);
@ -58,6 +66,7 @@ class LoginCheckpointController extends AbstractLoginController
$this->cache = $cache;
$this->repository = $repository;
$this->encrypter = $encrypter;
$this->recoveryTokenRepository = $recoveryTokenRepository;
}
/**
@ -76,21 +85,35 @@ class LoginCheckpointController extends AbstractLoginController
public function __invoke(LoginCheckpointRequest $request): JsonResponse
{
$token = $request->input('confirmation_token');
$recoveryToken = $request->input('recovery_token');
try {
/** @var \Pterodactyl\Models\User $user */
$user = $this->repository->find($this->cache->get($token, 0));
} catch (RecordNotFoundException $exception) {
return $this->sendFailedLoginResponse($request);
return $this->sendFailedLoginResponse($request, null, 'The authentication token provided has expired, please refresh the page and try again.');
}
$decrypted = $this->encrypter->decrypt($user->totp_secret);
// If we got a recovery token try to find one that matches for the user and then continue
// through the process (and delete the token).
if (! is_null($recoveryToken)) {
foreach ($user->recoveryTokens as $token) {
if (password_verify($recoveryToken, $token->token)) {
$this->recoveryTokenRepository->delete($token->id);
if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) {
$this->cache->delete($token);
return $this->sendLoginResponse($user, $request);
}
}
} else {
$decrypted = $this->encrypter->decrypt($user->totp_secret);
return $this->sendLoginResponse($user, $request);
if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) {
$this->cache->delete($token);
return $this->sendLoginResponse($user, $request);
}
}
return $this->sendFailedLoginResponse($request, $user);
return $this->sendFailedLoginResponse($request, $user, ! empty($recoveryToken) ? 'The recovery token provided is not valid.' : null);
}
}

View file

@ -103,7 +103,7 @@ class LoginController extends AbstractLoginController
$token = Str::random(64);
$this->cache->put($token, $user->id, Chronos::now()->addMinutes(5));
return JsonResponse::create([
return new JsonResponse([
'data' => [
'complete' => false,
'confirmation_token' => $token,