Merge branch 'develop' into feature/server-mounts
This commit is contained in:
commit
29876e023b
166 changed files with 5482 additions and 4130 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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'),
|
||||
]);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -9,6 +9,7 @@ use Pterodactyl\Http\Middleware\TrimStrings;
|
|||
use Pterodactyl\Http\Middleware\TrustProxies;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Pterodactyl\Http\Middleware\EncryptCookies;
|
||||
use Pterodactyl\Http\Middleware\Api\IsValidJson;
|
||||
use Pterodactyl\Http\Middleware\VerifyCsrfToken;
|
||||
use Pterodactyl\Http\Middleware\VerifyReCaptcha;
|
||||
use Pterodactyl\Http\Middleware\AdminAuthenticate;
|
||||
|
@ -28,12 +29,8 @@ use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings;
|
|||
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Pterodactyl\Http\Middleware\Server\AccessingValidServer;
|
||||
use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser;
|
||||
use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate;
|
||||
use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer;
|
||||
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
|
||||
use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer;
|
||||
use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer;
|
||||
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
|
||||
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
|
||||
use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings;
|
||||
|
@ -72,7 +69,7 @@ class Kernel extends HttpKernel
|
|||
RequireTwoFactorAuthentication::class,
|
||||
],
|
||||
'api' => [
|
||||
'throttle:240,1',
|
||||
IsValidJson::class,
|
||||
ApiSubstituteBindings::class,
|
||||
SetSessionDriver::class,
|
||||
'api..key:' . ApiKey::TYPE_APPLICATION,
|
||||
|
@ -80,10 +77,10 @@ class Kernel extends HttpKernel
|
|||
AuthenticateIPAccess::class,
|
||||
],
|
||||
'client-api' => [
|
||||
'throttle:240,1',
|
||||
StartSession::class,
|
||||
SetSessionDriver::class,
|
||||
AuthenticateSession::class,
|
||||
IsValidJson::class,
|
||||
SubstituteClientApiBindings::class,
|
||||
'api..key:' . ApiKey::TYPE_ACCOUNT,
|
||||
AuthenticateIPAccess::class,
|
||||
|
@ -104,7 +101,6 @@ class Kernel extends HttpKernel
|
|||
'auth.basic' => AuthenticateWithBasicAuth::class,
|
||||
'guest' => RedirectIfAuthenticated::class,
|
||||
'server' => AccessingValidServer::class,
|
||||
'subuser.auth' => AuthenticateAsSubuser::class,
|
||||
'admin' => AdminAuthenticate::class,
|
||||
'csrf' => VerifyCsrfToken::class,
|
||||
'throttle' => ThrottleRequests::class,
|
||||
|
@ -113,14 +109,6 @@ class Kernel extends HttpKernel
|
|||
'recaptcha' => VerifyReCaptcha::class,
|
||||
'node.maintenance' => MaintenanceMiddleware::class,
|
||||
|
||||
// Server specific middleware (used for authenticating access to resources)
|
||||
//
|
||||
// These are only used for individual server authentication, and not global
|
||||
// actions from other resources. They are defined in the route files.
|
||||
'server..database' => DatabaseBelongsToServer::class,
|
||||
'server..subuser' => SubuserBelongsToServer::class,
|
||||
'server..schedule' => ScheduleBelongsToServer::class,
|
||||
|
||||
// API Specific Middleware
|
||||
'api..key' => AuthenticateKey::class,
|
||||
];
|
||||
|
|
|
@ -65,7 +65,7 @@ class AuthenticateServerAccess
|
|||
}
|
||||
|
||||
if ($server->suspended) {
|
||||
throw new AccessDeniedHttpException('Cannot access a server that is marked as being suspended.');
|
||||
throw new AccessDeniedHttpException('This server is currenty suspended and the functionality requested is unavailable.');
|
||||
}
|
||||
|
||||
if (! $server->isInstalled()) {
|
||||
|
|
|
@ -5,8 +5,8 @@ namespace Pterodactyl\Http\Middleware\Api\Daemon;
|
|||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
@ -14,10 +14,15 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|||
class DaemonAuthenticate
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
|
||||
* @var \Pterodactyl\Repositories\Eloquent\NodeRepository
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Encryption\Encrypter
|
||||
*/
|
||||
private $encrypter;
|
||||
|
||||
/**
|
||||
* Daemon routes that this middleware should be skipped on.
|
||||
*
|
||||
|
@ -27,18 +32,13 @@ class DaemonAuthenticate
|
|||
'daemon.configuration',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Encryption\Encrypter
|
||||
*/
|
||||
private $encrypter;
|
||||
|
||||
/**
|
||||
* DaemonAuthenticate constructor.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
||||
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
|
||||
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $repository
|
||||
*/
|
||||
public function __construct(Encrypter $encrypter, NodeRepositoryInterface $repository)
|
||||
public function __construct(Encrypter $encrypter, NodeRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->encrypter = $encrypter;
|
||||
|
|
38
app/Http/Middleware/Api/IsValidJson.php
Normal file
38
app/Http/Middleware/Api/IsValidJson.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Middleware\Api;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
class IsValidJson
|
||||
{
|
||||
/**
|
||||
* Throw an exception if the request should be valid JSON data but there is an error while
|
||||
* 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())) {
|
||||
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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Http\Middleware\Server;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
class AuthenticateAsSubuser
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService
|
||||
*/
|
||||
private $keyProviderService;
|
||||
|
||||
/**
|
||||
* SubuserAccessAuthenticate constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService
|
||||
*/
|
||||
public function __construct(DaemonKeyProviderService $keyProviderService)
|
||||
{
|
||||
$this->keyProviderService = $keyProviderService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a subuser has permissions to access a server, if so set their access token.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$server = $request->attributes->get('server');
|
||||
|
||||
try {
|
||||
$token = $this->keyProviderService->handle($server, $request->user());
|
||||
} catch (RecordNotFoundException $exception) {
|
||||
throw new AccessDeniedHttpException('This account does not have permission to access this server.');
|
||||
}
|
||||
|
||||
$request->attributes->set('server_token', $token);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Middleware\Server;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class DatabaseBelongsToServer
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* DatabaseAccess constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(DatabaseRepositoryInterface $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a database being requested belongs to the currently loaded server.
|
||||
* If it does not, throw a 404 error, otherwise continue on with the request
|
||||
* and set an attribute with the database.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$server = $request->attributes->get('server');
|
||||
$database = $request->input('database') ?? $request->route()->parameter('database');
|
||||
|
||||
if (! is_digit($database)) {
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
$database = $this->repository->find($database);
|
||||
if (is_null($database) || $database->server_id !== $server->id) {
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
$request->attributes->set('database', $database);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Middleware\Server;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Contracts\Extensions\HashidsInterface;
|
||||
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class ScheduleBelongsToServer
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Extensions\HashidsInterface
|
||||
*/
|
||||
private $hashids;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* TaskAccess constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids
|
||||
* @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(HashidsInterface $hashids, ScheduleRepositoryInterface $repository)
|
||||
{
|
||||
$this->hashids = $hashids;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a task is assigned to the active server.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$server = $request->attributes->get('server');
|
||||
|
||||
$scheduleId = $this->hashids->decodeFirst($request->route()->parameter('schedule'), 0);
|
||||
$schedule = $this->repository->getScheduleWithTasks($scheduleId);
|
||||
|
||||
if ($schedule->server_id !== $server->id) {
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
$request->attributes->set('schedule', $schedule);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Middleware\Server;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
use Pterodactyl\Contracts\Extensions\HashidsInterface;
|
||||
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class SubuserBelongsToServer
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Extensions\HashidsInterface
|
||||
*/
|
||||
private $hashids;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* SubuserAccess constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids
|
||||
* @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(HashidsInterface $hashids, SubuserRepositoryInterface $repository)
|
||||
{
|
||||
$this->hashids = $hashids;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a user has permission to access and modify subuser.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$server = $request->attributes->get('server');
|
||||
|
||||
$hash = $request->route()->parameter('subuser', 0);
|
||||
$subuser = $this->repository->find($this->hashids->decodeFirst($hash, 0));
|
||||
if (is_null($subuser) || $subuser->server_id !== $server->id) {
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
if ($request->method() === 'PATCH') {
|
||||
if ($subuser->user_id === $request->user()->id) {
|
||||
throw new DisplayException(trans('exceptions.subusers.editing_self'));
|
||||
}
|
||||
}
|
||||
|
||||
$request->attributes->set('subuser', $subuser);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ class StoreNodeRequest extends ApplicationApiRequest
|
|||
/**
|
||||
* Validation rules to apply to this request.
|
||||
*
|
||||
* @param null|array $rules
|
||||
* @param array|null $rules
|
||||
* @return array
|
||||
*/
|
||||
public function rules(array $rules = null): array
|
||||
|
|
|
@ -21,7 +21,7 @@ class StoreScheduleRequest extends ViewScheduleRequest
|
|||
{
|
||||
return [
|
||||
'name' => 'required|string|min:1',
|
||||
'is_active' => 'boolean',
|
||||
'is_active' => 'filled|boolean',
|
||||
'minute' => 'required|string',
|
||||
'hour' => 'required|string',
|
||||
'day_of_month' => 'required|string',
|
||||
|
|
|
@ -25,7 +25,7 @@ class StoreTaskRequest extends ViewScheduleRequest
|
|||
{
|
||||
return [
|
||||
'action' => 'required|in:command,power,backup',
|
||||
'payload' => 'required_unless:action,backup|string',
|
||||
'payload' => 'required_unless:action,backup|string|nullable',
|
||||
'time_offset' => 'required|numeric|min:0|max:900',
|
||||
'sequence_id' => 'sometimes|required|numeric|min:1',
|
||||
];
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
namespace Pterodactyl\Http\Requests\Api\Client\Servers;
|
||||
|
||||
use Pterodactyl\Models\Permission;
|
||||
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||
|
||||
class SendCommandRequest extends GetServerRequest
|
||||
class SendCommandRequest extends ClientApiRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the API user has permission to perform this action.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Pterodactyl\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class LoginCheckpointRequest extends FormRequest
|
||||
|
@ -25,7 +26,20 @@ class LoginCheckpointRequest extends FormRequest
|
|||
{
|
||||
return [
|
||||
'confirmation_token' => 'required|string',
|
||||
'authentication_code' => 'required|numeric',
|
||||
'authentication_code' => [
|
||||
'nullable',
|
||||
'numeric',
|
||||
Rule::requiredIf(function () {
|
||||
return empty($this->input('recovery_token'));
|
||||
}),
|
||||
],
|
||||
'recovery_token' => [
|
||||
'nullable',
|
||||
'string',
|
||||
Rule::requiredIf(function () {
|
||||
return empty($this->input('authentication_code'));
|
||||
}),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue