Update all the middlewares

This commit is contained in:
Dane Everitt 2017-10-29 12:37:25 -05:00
parent e0d03513e4
commit 79decafdc8
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
16 changed files with 161 additions and 100 deletions

View File

@ -28,6 +28,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
* Server creation page now only asks for a node to deploy to, rather than requiring a location and then a node. * Server creation page now only asks for a node to deploy to, rather than requiring a location and then a node.
* Database passwords are now hidden by default and will only show if clicked on. In addition, database view in ACP now indicates that passwords must be viewed on the front-end. * Database passwords are now hidden by default and will only show if clicked on. In addition, database view in ACP now indicates that passwords must be viewed on the front-end.
* Localhost cannot be used as a connection address in the environment configuration script. `127.0.0.1` is allowed. * Localhost cannot be used as a connection address in the environment configuration script. `127.0.0.1` is allowed.
* Application locale can now be quickly set using an environment variable `APP_LOCALE` rather than having to edit core files.
### Fixed ### Fixed
* Unable to change the daemon secret for a server via the Admin CP. * Unable to change the daemon secret for a server via the Admin CP.

View File

@ -6,6 +6,7 @@ use Pterodactyl\Http\Middleware\DaemonAuthenticate;
use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Routing\Middleware\SubstituteBindings;
use Pterodactyl\Http\Middleware\AccessingValidServer; use Pterodactyl\Http\Middleware\AccessingValidServer;
use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser;
use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer; use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer;
use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer;
use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer;
@ -66,7 +67,7 @@ class Kernel extends HttpKernel
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class, 'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class,
'server' => AccessingValidServer::class, 'server' => AccessingValidServer::class,
'subuser.auth' => \Pterodactyl\Http\Middleware\SubuserAccessAuthenticate::class, 'subuser.auth' => AuthenticateAsSubuser::class,
'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class, 'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class,
'daemon-old' => DaemonAuthenticate::class, 'daemon-old' => DaemonAuthenticate::class,
'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class, 'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class,

View File

@ -10,6 +10,8 @@
namespace Pterodactyl\Http\Middleware; namespace Pterodactyl\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
class AdminAuthenticate class AdminAuthenticate
{ {
@ -20,18 +22,10 @@ class AdminAuthenticate
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if (! $request->user()) { if (! $request->user() || ! $request->user()->root_admin) {
if ($request->expectsJson() || $request->json()) { throw new HttpException(403, 'Access Denied');
return response('Unauthorized.', 401);
} else {
return redirect()->guest('auth/login');
}
}
if (! $request->user()->root_admin) {
return abort(403);
} }
return $next($request); return $next($request);

View File

@ -3,6 +3,7 @@
namespace Pterodactyl\Http\Middleware; namespace Pterodactyl\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Auth\Guard;
class Authenticate class Authenticate
@ -31,7 +32,7 @@ class Authenticate
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if ($this->auth->guest()) { if ($this->auth->guest()) {
if ($request->ajax()) { if ($request->ajax()) {

View File

@ -33,9 +33,12 @@ use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
class DaemonAuthenticate class DaemonAuthenticate
{ {
/** /**
* Daemon routes that this middleware should be skipped on.
* @var array * @var array
*/ */
protected $except = ['daemon.configuration']; protected $except = [
'daemon.configuration',
];
/** /**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
@ -63,6 +66,10 @@ class DaemonAuthenticate
*/ */
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if (in_array($request->route()->getName(), $this->except)) {
return $next($request);
}
$token = $request->bearerToken(); $token = $request->bearerToken();
if (is_null($token)) { if (is_null($token)) {

View File

@ -10,35 +10,36 @@
namespace Pterodactyl\Http\Middleware; namespace Pterodactyl\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Models\Node; use Pterodactyl\Models\Node;
use Illuminate\Contracts\Auth\Guard; use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
class DaemonAuthenticate class DaemonAuthenticate
{ {
/**
* The Guard implementation.
*
* @var \Illuminate\Contracts\Auth\Guard
*/
protected $auth;
/** /**
* An array of route names to not apply this middleware to. * An array of route names to not apply this middleware to.
* *
* @var array * @var array
*/ */
protected $except = [ private $except = [
'daemon.configuration', 'daemon.configuration',
]; ];
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
*/
private $repository;
/** /**
* Create a new filter instance. * Create a new filter instance.
* *
* @param \Illuminate\Contracts\Auth\Guard $auth * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
*/ */
public function __construct(Guard $auth) public function __construct(NodeRepositoryInterface $repository)
{ {
$this->auth = $auth; $this->repository = $repository;
} }
/** /**
@ -48,21 +49,24 @@ class DaemonAuthenticate
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if (in_array($request->route()->getName(), $this->except)) { if (in_array($request->route()->getName(), $this->except)) {
return $next($request); return $next($request);
} }
if (! $request->header('X-Access-Node')) { if (! $request->header('X-Access-Node')) {
return abort(403); throw new HttpException(403);
} }
$node = Node::where('daemonSecret', $request->header('X-Access-Node'))->first(); try {
if (! $node) { $node = $this->repository->findWhere(['daemonSecret' => $request->header('X-Access-Node')]);
return abort(401); } catch (RecordNotFoundException $exception) {
throw new HttpException(401);
} }
$request->attributes->set('node', $node);
return $next($request); return $next($request);
} }
} }

View File

@ -11,6 +11,5 @@ class EncryptCookies extends BaseEncrypter
* *
* @var array * @var array
*/ */
protected $except = [ protected $except = [];
];
} }

View File

@ -9,14 +9,28 @@
namespace Pterodactyl\Http\Middleware; namespace Pterodactyl\Http\Middleware;
use Auth;
use Closure; use Closure;
use Session; use Illuminate\Http\Request;
use Settings;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Contracts\Config\Repository;
class LanguageMiddleware class LanguageMiddleware
{ {
/**
* @var \Illuminate\Contracts\Config\Repository
*/
private $config;
/**
* LanguageMiddleware constructor.
*
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(Repository $config)
{
$this->config = $config;
}
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
@ -24,17 +38,9 @@ class LanguageMiddleware
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle(Request $request, Closure $next)
{ {
// if (Session::has('applocale')) { App::setLocale($this->config->get('app.locale', 'en'));
// App::setLocale(Session::get('applocale'));
// } elseif (Auth::check() && isset(Auth::user()->language)) {
// Session::put('applocale', Auth::user()->language);
// App::setLocale(Auth::user()->language);
// } else {
// App::setLocale(Settings::get('default_language', 'en'));
// }
App::setLocale('en');
return $next($request); return $next($request);
} }

View File

@ -3,10 +3,26 @@
namespace Pterodactyl\Http\Middleware; namespace Pterodactyl\Http\Middleware;
use Closure; use Closure;
use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request;
use Illuminate\Auth\AuthManager;
class RedirectIfAuthenticated class RedirectIfAuthenticated
{ {
/**
* @var \Illuminate\Contracts\Auth\Guard
*/
private $authManager;
/**
* RedirectIfAuthenticated constructor.
*
* @param \Illuminate\Auth\AuthManager $authManager
*/
public function __construct(AuthManager $authManager)
{
$this->authManager = $authManager;
}
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
@ -15,9 +31,9 @@ class RedirectIfAuthenticated
* @param string|null $guard * @param string|null $guard
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next, $guard = null) public function handle(Request $request, Closure $next, string $guard = null)
{ {
if (Auth::guard($guard)->check()) { if ($this->authManager->guard($guard)->check()) {
return redirect(route('index')); return redirect(route('index'));
} }

View File

@ -10,6 +10,7 @@
namespace Pterodactyl\Http\Middleware; namespace Pterodactyl\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request;
use Krucas\Settings\Settings; use Krucas\Settings\Settings;
use Prologue\Alerts\AlertsMessageBag; use Prologue\Alerts\AlertsMessageBag;
@ -22,28 +23,35 @@ class RequireTwoFactorAuthentication
/** /**
* @var \Prologue\Alerts\AlertsMessageBag * @var \Prologue\Alerts\AlertsMessageBag
*/ */
protected $alert; private $alert;
/** /**
* @var \Krucas\Settings\Settings * @var \Krucas\Settings\Settings
*/ */
protected $settings; private $settings;
/** /**
* All TOTP related routes. * The names of routes that should be accessable without 2FA enabled.
* *
* @var array * @var array
*/ */
protected $ignoreRoutes = [ protected $except = [
'account.security', 'account.security',
'account.security.revoke', 'account.security.revoke',
'account.security.totp', 'account.security.totp',
'account.security.totp.set', 'account.security.totp.set',
'account.security.totp.disable', 'account.security.totp.disable',
'auth.totp', 'auth.totp',
'auth.logout', 'auth.logout',
]; ];
/**
* The route to redirect a user to to enable 2FA.
*
* @var string
*/
protected $redirectRoute = 'account.security';
/** /**
* RequireTwoFactorAuthentication constructor. * RequireTwoFactorAuthentication constructor.
* *
@ -63,7 +71,7 @@ class RequireTwoFactorAuthentication
* @param \Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle($request, Closure $next) public function handle(Request $request, Closure $next)
{ {
// Ignore non-users // Ignore non-users
if (! $request->user()) { if (! $request->user()) {
@ -71,7 +79,7 @@ class RequireTwoFactorAuthentication
} }
// Skip the 2FA pages // Skip the 2FA pages
if (in_array($request->route()->getName(), $this->ignoreRoutes)) { if (in_array($request->route()->getName(), $this->except)) {
return $next($request); return $next($request);
} }
@ -93,8 +101,8 @@ class RequireTwoFactorAuthentication
break; break;
} }
$this->alert->danger('The administrator has required 2FA to be enabled. You must enable it before you can do any other action.')->flash(); $this->alert->danger(trans('auth.2fa_must_be_enabled'))->flash();
return redirect()->route('account.security'); return redirect()->route($this->redirectRoute);
} }
} }

View File

@ -7,7 +7,7 @@
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
namespace Pterodactyl\Http\Middleware; namespace Pterodactyl\Http\Middleware\Server;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -16,17 +16,17 @@ use Illuminate\Auth\AuthenticationException;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
class SubuserAccessAuthenticate class AuthenticateAsSubuser
{ {
/** /**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService
*/ */
protected $keyProviderService; private $keyProviderService;
/** /**
* @var \Illuminate\Contracts\Session\Session * @var \Illuminate\Contracts\Session\Session
*/ */
protected $session; private $session;
/** /**
* SubuserAccessAuthenticate constructor. * SubuserAccessAuthenticate constructor.
@ -34,10 +34,8 @@ class SubuserAccessAuthenticate
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService
* @param \Illuminate\Contracts\Session\Session $session * @param \Illuminate\Contracts\Session\Session $session
*/ */
public function __construct( public function __construct(DaemonKeyProviderService $keyProviderService, Session $session)
DaemonKeyProviderService $keyProviderService, {
Session $session
) {
$this->keyProviderService = $keyProviderService; $this->keyProviderService = $keyProviderService;
$this->session = $session; $this->session = $session;
} }
@ -55,16 +53,17 @@ class SubuserAccessAuthenticate
*/ */
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
$server = $this->session->get('server_data.model'); $server = $request->attributes->get('server');
try { try {
$token = $this->keyProviderService->handle($server->id, $request->user()->id); $token = $this->keyProviderService->handle($server->id, $request->user()->id);
$this->session->now('server_data.token', $token);
$request->attributes->set('server_token', $token);
} catch (RecordNotFoundException $exception) { } catch (RecordNotFoundException $exception) {
throw new AuthenticationException('This account does not have permission to access this server.'); throw new AuthenticationException('This account does not have permission to access this server.');
} }
$this->session->now('server_data.token', $token);
$request->attributes->set('server_token', $token);
return $next($request); return $next($request);
} }
} }

View File

@ -3,6 +3,7 @@
namespace Pterodactyl\Http\Middleware\Server; namespace Pterodactyl\Http\Middleware\Server;
use Closure; use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Contracts\Extensions\HashidsInterface;
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@ -41,7 +42,7 @@ class ScheduleBelongsToServer
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/ */
public function handle($request, Closure $next) public function handle(Request $request, Closure $next)
{ {
$server = $request->attributes->get('server'); $server = $request->attributes->get('server');

View File

@ -3,6 +3,7 @@
namespace Pterodactyl\Http\Middleware\Server; namespace Pterodactyl\Http\Middleware\Server;
use Closure; use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Contracts\Extensions\HashidsInterface; use Pterodactyl\Contracts\Extensions\HashidsInterface;
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
@ -43,7 +44,7 @@ class SubuserBelongsToServer
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/ */
public function handle($request, Closure $next) public function handle(Request $request, Closure $next)
{ {
$server = $request->attributes->get('server'); $server = $request->attributes->get('server');

View File

@ -3,28 +3,46 @@
namespace Pterodactyl\Http\Middleware; namespace Pterodactyl\Http\Middleware;
use Closure; use Closure;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Pterodactyl\Events\Auth\FailedCaptcha; use Pterodactyl\Events\Auth\FailedCaptcha;
use Illuminate\Contracts\Config\Repository;
class VerifyReCaptcha class VerifyReCaptcha
{ {
/**
* @var \Illuminate\Contracts\Config\Repository
*/
private $config;
/**
* VerifyReCaptcha constructor.
*
* @param \Illuminate\Contracts\Config\Repository $config
*/
public function __construct(Repository $config)
{
$this->config = $config;
}
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Closure $next * @param \Closure $next
* @return \Illuminate\Http\RediectResponse * @return \Illuminate\Http\RedirectResponse|mixed
*/ */
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
if (! config('recaptcha.enabled')) { if (! $this->config->get('recaptcha.enabled')) {
return $next($request); return $next($request);
} }
if ($request->has('g-recaptcha-response')) { if ($request->has('g-recaptcha-response')) {
$client = new \GuzzleHttp\Client(); $client = new Client();
$res = $client->post(config('recaptcha.domain'), [ $res = $client->post($this->config->get('recaptcha.domain'), [
'form_params' => [ 'form_params' => [
'secret' => config('recaptcha.secret_key'), 'secret' => $this->config->get('recaptcha.secret_key'),
'response' => $request->input('g-recaptcha-response'), 'response' => $request->input('g-recaptcha-response'),
], ],
]); ]);
@ -32,29 +50,33 @@ class VerifyReCaptcha
if ($res->getStatusCode() === 200) { if ($res->getStatusCode() === 200) {
$result = json_decode($res->getBody()); $result = json_decode($res->getBody());
$verified = function ($result, $request) { if ($result->success && (! $this->config->get('recaptcha.verify_domain') || $this->isResponseVerified($result, $request))) {
if (! config('recaptcha.verify_domain')) {
return false;
}
$url = parse_url($request->url());
if (! array_key_exists('host', $url)) {
return false;
}
return $result->hostname === $url['host'];
};
if ($result->success && (! config('recaptcha.verify_domain') || $verified($result, $request))) {
return $next($request); return $next($request);
} }
} }
} }
// Emit an event and return to the previous view with an error (only the captcha error will be shown!) // Emit an event and return to the previous view with an error (only the captcha error will be shown!)
event(new FailedCaptcha($request->ip(), (! isset($result->hostname) ?: $result->hostname))); event(new FailedCaptcha($request->ip(), (! isset($result) ?: object_get($result, 'hostname'))));
return back()->withErrors(['g-recaptcha-response' => trans('strings.captcha_invalid')])->withInput(); return redirect()->back()->withErrors(['g-recaptcha-response' => trans('strings.captcha_invalid')])->withInput();
}
/**
* Determine if the response from the recaptcha servers was valid.
*
* @param object $result
* @param \Illuminate\Http\Request $request
* @return bool
*/
private function isResponseVerified(object $result, Request $request): bool
{
if (! $this->config->get('recaptcha.verify_domain')) {
return false;
}
$url = parse_url($request->url());
return $result->hostname === array_get($url, 'host');
} }
} }

View File

@ -66,7 +66,7 @@ return [
| |
*/ */
'locale' => 'en', 'locale' => env('APP_LOCALE', 'en'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -18,4 +18,5 @@ return [
'2fa_required' => '2-Factor Authentication', '2fa_required' => '2-Factor Authentication',
'2fa_failed' => 'The 2FA token provided was invalid.', '2fa_failed' => 'The 2FA token provided was invalid.',
'totp_failed' => 'There was an error while attempting to validate TOTP.', 'totp_failed' => 'There was an error while attempting to validate TOTP.',
'2fa_must_be_enabled' => 'The administrator has required that 2-Factor Authentication be enabled for your account in order to use the Panel.',
]; ];