Initial Commit of Files

PufferPanel v0.9 (Laravel) is now Pterodactyl 1.0
This commit is contained in:
Dane Everitt 2015-12-06 13:58:49 -05:00
commit 1489f7a694
154 changed files with 10159 additions and 0 deletions

View file

@ -0,0 +1,33 @@
<?php
namespace Pterodactyl\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Foundation\Inspiring;
class Inspire extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'inspire';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Display an inspiring quote';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->comment(PHP_EOL.Inspiring::quote().PHP_EOL);
}
}

30
app/Console/Kernel.php Normal file
View file

@ -0,0 +1,30 @@
<?php
namespace Pterodactyl\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
\Pterodactyl\Console\Commands\Inspire::class,
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('inspire')
->hourly();
}
}

8
app/Events/Event.php Normal file
View file

@ -0,0 +1,8 @@
<?php
namespace Pterodactyl\Events;
abstract class Event
{
//
}

View file

@ -0,0 +1,8 @@
<?php
namespace Pterodactyl\Exceptions;
class AccountNotFoundException extends \Exception
{
}

View file

@ -0,0 +1,8 @@
<?php
namespace Pterodactyl\Exceptions;
class DisplayException extends \Exception
{
}

View file

@ -0,0 +1,78 @@
<?php
namespace Pterodactyl\Exceptions;
use Exception;
use DisplayException;
use Debugbar;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that should not be reported.
*
* @var array
*/
protected $dontReport = [
HttpException::class,
ModelNotFoundException::class,
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Exception $e
* @return void
*/
public function report(Exception $e)
{
return parent::report($e);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
if ($e instanceof ModelNotFoundException) {
$e = new NotFoundHttpException($e->getMessage(), $e);
}
if ($request->isXmlHttpRequest() || $request->ajax() || $request->is('/api/*')) {
$exception = 'An exception occured while attempting to perform this action, please try again.';
if ($e instanceof DisplayException) {
$exception = $e->getMessage();
}
// Live environment, just return a nice error.
if(!env('APP_DEBUG', false)) {
return response()->json([
'error' => $exception
], 500);
}
// If we are debugging, return the exception in it's full manner.
return response()->json([
'error' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile(),
'line' => $e->getLine()
], 500);
}
return parent::render($request, $e);
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Pterodactyl\Http\Controllers\API;
use Gate;
use Log;
use Debugbar;
use Pterodactyl\Models\API;
use Pterodactyl\Models\User;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Constructor
*/
public function __construct()
{
$this->middleware('api');
}
public function getAllUsers(Request $request)
{
// Policies don't work if the user isn't logged in for whatever reason in Laravel...
if(!API::checkPermission($request->header('X-Authorization'), 'get-users')) {
return API::noPermissionError();
}
return response()->json([
'users' => User::all()
]);
}
/**
* Returns JSON response about a user given their ID.
* If fields are provided only those fields are returned.
*
* Does not return protected fields (i.e. password & totp_secret)
*
* @param Request $request
* @param int $id
* @param string $fields
* @return Response
*/
public function getUser(Request $request, $id, $fields = null)
{
// Policies don't work if the user isn't logged in for whatever reason in Laravel...
if(!API::checkPermission($request->header('X-Authorization'), 'get-users')) {
return API::noPermissionError();
}
if (is_null($fields)) {
return response()->json(User::find($id));
}
$query = User::where('id', $id);
$explode = explode(',', $fields);
foreach($explode as &$exploded) {
if(!empty($exploded)) {
$query->addSelect($exploded);
}
}
try {
return response()->json($query->get());
} catch (\Exception $e) {
if ($e instanceof \Illuminate\Database\QueryException) {
return response()->json([
'error' => 'One of the fields provided in your argument list is invalid.'
], 500);
}
throw $e;
}
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin;
use Debugbar;
use Pterodactyl\Models\User;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
class AccountsController extends Controller
{
/**
* Controller Constructor
*/
public function __construct()
{
// All routes in this controller are protected by the authentication middleware.
$this->middleware('auth');
$this->middleware('admin');
}
public function getIndex(Request $request)
{
return view('admin.accounts.index', [
'users' => User::paginate(20)
]);
}
public function getNew(Request $request)
{
//
}
public function getView(Request $request, $id)
{
//
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin;
use Debugbar;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
class BaseController extends Controller
{
/**
* Controller Constructor
*/
public function __construct()
{
// All routes in this controller are protected by the authentication middleware.
$this->middleware('auth');
$this->middleware('admin');
}
public function getIndex(Request $request)
{
return view('admin.index');
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin;
use Debugbar;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Node;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ServersController extends Controller
{
/**
* Controller Constructor
*/
public function __construct()
{
// All routes in this controller are protected by the authentication middleware.
$this->middleware('auth');
$this->middleware('admin');
}
public function getIndex(Request $request)
{
return view('admin.servers.index', [
'servers' => Server::select('servers.*', 'nodes.name as a_nodeName', 'users.email as a_ownerEmail')
->join('nodes', 'servers.node', '=', 'nodes.id')
->join('users', 'servers.owner', '=', 'users.id')
->paginate(20),
]);
}
public function getNew(Request $request)
{
//
}
public function getView(Request $request, $id)
{
//
}
}

View file

@ -0,0 +1,97 @@
<?php
namespace Pterodactyl\Http\Controllers\Auth;
use Pterodactyl\Models\User;
use Validator;
use Auth;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
|
*/
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
/**
* Post-Authentication redirect location.
*
* @var string
*/
protected $redirectPath = '/';
/**
* Failed post-authentication redirect location.
*
* @var string
*/
protected $loginPath = '/auth/login';
/**
* Lockout time for failed login requests.
*
* @var integer
*/
protected $lockoutTime = 120;
/**
* After how many attempts should logins be throttled and locked.
*
* @var integer
*/
protected $maxLoginAttempts = 5;
/**
* Create a new authentication controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:8',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => password_hash($data['password']),
]);
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Pterodactyl\Http\Controllers\Auth;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class PasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Create a new password controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
}

View file

@ -0,0 +1,222 @@
<?php
namespace Pterodactyl\Http\Controllers\Base;
use Auth;
use Debugbar;
use Google2FA;
use Log;
use Alert;
use Pterodactyl\Exceptions\AccountNotFoundException;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
class IndexController extends Controller
{
/**
* Controller Constructor
*/
public function __construct()
{
// All routes in this controller are protected by the authentication middleware.
$this->middleware('auth');
}
/**
* Returns listing of user's servers.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function getIndex(Request $request)
{
return view('base.index', [
'servers' => Server::getUserServers(),
]);
}
/**
* Returns TOTP Management Page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function getAccountTotp(Request $request)
{
return view('base.totp');
}
/**
* Generates TOTP Secret and returns popup data for user to verify
* that they can generate a valid response.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function putAccountTotp(Request $request)
{
try {
$totpSecret = User::setTotpSecret(Auth::user()->id);
} catch (\Exception $e) {
if ($e instanceof AccountNotFoundException) {
return response($e->getMessage(), 500);
}
throw $e;
}
return response()->json([
'qrImage' => Google2FA::getQRCodeGoogleUrl(
'Pterodactyl',
Auth::user()->email,
$totpSecret
),
'secret' => $totpSecret
]);
}
/**
* Verifies that 2FA token recieved is valid and will work on the account.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function postAccountTotp(Request $request)
{
if (!$request->has('token')) {
return response('No input \'token\' defined.', 500);
}
try {
if(User::toggleTotp(Auth::user()->id, $request->input('token'))) {
return response('true');
}
return response('false');
} catch (\Exception $e) {
if ($e instanceof AccountNotFoundException) {
return response($e->getMessage(), 500);
}
throw $e;
}
}
/**
* Disables TOTP on an account.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function deleteAccountTotp(Request $request)
{
if (!$request->has('token')) {
Alert::danger('Missing required `token` field in request.')->flash();
return redirect()->route('account.totp');
}
try {
if(User::toggleTotp(Auth::user()->id, $request->input('token'))) {
return redirect()->route('account.totp');
}
Alert::danger('Unable to disable TOTP on this account, was the token correct?')->flash();
return redirect()->route('account.totp');
} catch (\Exception $e) {
if ($e instanceof AccountNotFoundException) {
Alert::danger('An error occured while attempting to perform this action.')->flash();
return redirect()->route('account.totp');
}
throw $e;
}
}
/**
* Display base account information page.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function getAccount(Request $request)
{
return view('base.account');
}
/**
* Update an account email.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function postAccountEmail(Request $request)
{
$this->validate($request, [
'new_email' => 'required|email',
'password' => 'required'
]);
if (!password_verify($request->input('password'), Auth::user()->password)) {
Alert::danger('The password provided was not valid for this account.')->flash();
return redirect()->route('account');
}
// Met Validation, lets roll out.
try {
User::setEmail(Auth::user()->id, $request->input('new_email'));
Alert::success('Your email address has successfully been updated.')->flash();
return redirect()->route('account');
} catch (\Exception $e) {
if ($e instanceof AccountNotFoundException || $e instanceof DisplayException) {
Alert::danger($e->getMessage())->flash();
return redirect()->route('account');
}
throw $e;
}
}
/**
* Update an account password.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function postAccountPassword(Request $request)
{
$this->validate($request, [
'current_password' => 'required',
'new_password' => 'required|confirmed|different:current_password|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})',
'new_password_confirmation' => 'required'
]);
if (!password_verify($request->input('current_password'), Auth::user()->password)) {
Alert::danger('The password provided was not valid for this account.')->flash();
return redirect()->route('account');
}
// Met Validation, lets roll out.
try {
User::setPassword(Auth::user()->id, $request->input('new_password'));
Alert::success('Your password has successfully been updated.')->flash();
return redirect()->route('account');
} catch (\Exception $e) {
if ($e instanceof AccountNotFoundException || $e instanceof DisplayException) {
Alert::danger($e->getMessage())->flash();
return redirect()->route('account');
}
throw $e;
}
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
abstract class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

View file

@ -0,0 +1,193 @@
<?php
namespace Pterodactyl\Http\Controllers\Scales;
use \Exception;
use Log;
use Debugbar;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Node;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Helpers;
use Pterodactyl\Http\Controllers\Controller;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class FileController extends Controller
{
/**
* The Eloquent Model associated with the requested server.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $server;
/**
* The Eloquent Model for the node corresponding with the requested server.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $node;
/**
* The Guzzle Client associated with the requested server and node.
*
* @var \GuzzleHttp\Client
*/
protected $client;
/**
* The Guzzle Client headers associated with the requested server and node.
* (non-administrative headers)
*
* @var array
*/
protected $headers;
/**
* Constructor
*
* @param string $server The server Short UUID
*/
public function __construct($uuid)
{
$this->server = Server::getByUUID($uuid);
$this->node = Node::getByID($this->server->node);
$this->client = Node::guzzleRequest($this->server->node);
$this->headers = Server::getGuzzleHeaders($uuid);
}
/**
* Get the contents of a requested file for the server.
*
* @param string $file
* @return string
*/
public function returnFileContents($file)
{
if (empty($file)) {
throw new Exception('Not all parameters were properly passed to the function.');
}
$file = (object) pathinfo($file);
if (!in_array($file->extension, Helpers::editableFiles())) {
throw new DisplayException('You do not have permission to edit this type of file.');
}
$file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/';
$res = $this->client->request('GET', '/server/file/' . rawurlencode($file->dirname.$file->basename), [
'headers' => $this->headers
]);
$json = json_decode($res->getBody());
if($res->getStatusCode() !== 200 || !isset($json->contents)) {
throw new DisplayException('Scales provided a non-200 error code: HTTP\\' . $res->getStatusCode());
}
return $json;
}
/**
* Save the contents of a requested file on the Scales instance.
*
* @param string $file
* @param string $content
* @return boolean
*/
public function saveFileContents($file, $content)
{
if (empty($file)) {
throw new Exception('A valid file and path must be specified to save a file.');
}
$file = (object) pathinfo($file);
if(!in_array($file->extension, Helpers::editableFiles())) {
throw new DisplayException('You do not have permission to edit this type of file.');
}
$file->dirname = (in_array($file->dirname, ['.', './', '/'])) ? null : trim($file->dirname, '/') . '/';
$res = $this->client->request('PUT', '/server/file/' . rawurlencode($file->dirname.$file->basename), [
'headers' => $this->headers,
'form_params' => [
'contents' => $content
]
]);
if ($res->getStatusCode() !== 204) {
throw new DisplayException('An error occured while attempting to save this file. Scales said: ' . $res->getBody());
}
return true;
}
/**
* Returns a listing of all files and folders within a specified Scales directory.
*
* @param string $directory
* @return object
*/
public function returnDirectoryListing($directory)
{
if (empty($directory)) {
throw new Exception('A valid directory must be specified in order to list its contents.');
}
$res = $this->client->request('GET', '/server/directory/' . $directory, [
'headers' => $this->headers
]);
$json = json_decode($res->getBody());
if($res->getStatusCode() !== 200) {
throw new DisplayException('An error occured while attempting to save this file. Scales said: ' . $res->getBody());
}
// Iterate through results
$files = [];
$folders = [];
foreach($json as &$value) {
if ($value->file !== true) {
// @TODO Handle Symlinks
$folders = array_merge($folders, [[
'entry' => $value->name,
'directory' => trim($directory, '/'),
'size' => null,
'date' => strtotime($value->modified)
]]);
} else {
$files = array_merge($files, [[
'entry' => $value->name,
'directory' => trim($directory, '/'),
'extension' => pathinfo($value->name, PATHINFO_EXTENSION),
'size' => Helpers::bytesToHuman($value->size),
'date' => strtotime($value->modified)
]]);
}
}
return (object) [
'files' => $files,
'folders' => $folders,
];
}
}

View file

@ -0,0 +1,182 @@
<?php
namespace Pterodactyl\Http\Controllers\Server;
use Log;
use Debugbar;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Node;
use Pterodactyl\Http\Helpers;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Scales\FileController;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class AjaxController extends Controller
{
/**
* @var array
*/
protected $files = [];
/**
* @var array
*/
protected $folders = [];
/**
* @var string
*/
protected $directory;
/**
* Controller Constructor
*/
public function __construct()
{
// All routes in this controller are protected by the authentication middleware.
$this->middleware('auth');
// Routes in this file are also checked aganist the server middleware. If the user
// does not have permission to view the server it will not load.
$this->middleware('server');
}
/**
* Returns true or false depending on the power status of the requested server.
*
* @param \Illuminate\Http\Request $request
* @param string $uuid
* @return \Illuminate\Contracts\View\View
*/
public function getStatus(Request $request, $uuid)
{
$server = Server::getByUUID($uuid);
$client = Node::guzzleRequest($server->node);
try {
$res = $client->request('GET', '/server', [
'headers' => Server::getGuzzleHeaders($uuid)
]);
if($res->getStatusCode() === 200) {
$json = json_decode($res->getBody());
if (isset($json->status) && $json->status === 1) {
return 'true';
}
}
} catch (RequestException $e) {
Debugbar::error($e->getMessage());
Log::notice('An exception was raised while attempting to contact a Scales instance to get server status information.', [
'exception' => $e->getMessage(),
'path' => $request->path()
]);
}
return 'false';
}
/**
* Returns a listing of files in a given directory for a server.
*
* @param \Illuminate\Http\Request $request
* @param string $uuid`
* @return \Illuminate\Contracts\View\View
*/
public function postDirectoryList(Request $request, $uuid)
{
$server = Server::getByUUID($uuid);
$this->directory = '/' . trim(urldecode($request->input('directory', '/')), '/');
$this->authorize('list-files', $server);
$prevDir = [
'header' => ($this->directory !== '/') ? $this->directory : ''
];
if ($this->directory !== '/') {
$prevDir['first'] = true;
}
// Determine if we should show back links in the file browser.
// This code is strange, and could probably be rewritten much better.
$goBack = explode('/', rtrim($this->directory, '/'));
if (isset($goBack[2]) && !empty($goBack[2])) {
$prevDir['show'] = true;
$prevDir['link'] = '/' . trim(str_replace(end($goBack), '', $this->directory), '/');
$prevDir['link_show'] = trim($prevDir['link'], '/');
}
$controller = new FileController($uuid);
try {
$directoryContents = $controller->returnDirectoryListing($this->directory);
} catch (\Exception $e) {
Debugbar::addException($e);
$exception = 'An error occured while attempting to load the requested directory, please try again.';
if ($e instanceof DisplayException) {
$exception = $e->getMessage();
}
return response($exception, 500);
}
return view('server.files.list', [
'server' => $server,
'files' => $directoryContents->files,
'folders' => $directoryContents->folders,
'extensions' => Helpers::editableFiles(),
'directory' => $prevDir
]);
}
/**
* Handles a POST request to save a file.
*
* @param Request $request
* @param string $uuid
* @return \Illuminate\Http\Response
*/
public function postSaveFile(Request $request, $uuid)
{
$server = Server::getByUUID($uuid);
$this->authorize('save-files', $server);
$controller = new FileController($uuid);
try {
$controller->saveFileContents($request->input('file'), $request->input('contents'));
return response(null, 204);
} catch (\Exception $e) {
Debugbar::addException($e);
$exception = 'An error occured while attempting to save that file, please try again.';
if ($e instanceof DisplayException) {
$exception = $e->getMessage();
}
return response($exception, 500);
}
}
}

View file

@ -0,0 +1,162 @@
<?php
namespace Pterodactyl\Http\Controllers\Server;
use Auth;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\Download;
use Debugbar;
use Uuid;
use Alert;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Scales\FileController;
use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ServerController extends Controller
{
/**
* Controller Constructor
*
* @return void
*/
public function __construct()
{
// All routes in this controller are protected by the authentication middleware.
$this->middleware('auth');
// Routes in this file are also checked aganist the server middleware. If the user
// does not have permission to view the server it will not load.
$this->middleware('server');
}
/**
* Renders server index page for specified server.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function getIndex(Request $request)
{
$server = Server::getByUUID($request->route()->server);
return view('server.index', [
'server' => $server,
'node' => Node::find($server->node)
]);
}
/**
* Renders file overview page.
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*/
public function getFiles(Request $request)
{
$server = Server::getByUUID($request->route()->server);
$this->authorize('list-files', $server);
return view('server.files.index', [
'server' => $server,
'node' => Node::find($server->node)
]);
}
/**
* Renders add file page.
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*/
public function getAddFile(Request $request)
{
$server = Server::getByUUID($request->route()->server);
$this->authorize('add-files', $server);
return view('server.files.add', [
'server' => $server,
'node' => Node::find($server->node),
'directory' => (in_array($request->get('dir'), [null, '/', ''])) ? '' : trim($request->get('dir'), '/') . '/'
]);
}
/**
* Renders edit file page for a given file.
*
* @param Request $request
* @param string $uuid
* @param string $file
* @return \Illuminate\Contracts\View\View
*/
public function getEditFile(Request $request, $uuid, $file)
{
$server = Server::getByUUID($uuid);
$this->authorize('edit-files', $server);
$fileInfo = (object) pathinfo($file);
$controller = new FileController($uuid);
try {
$fileContent = $controller->returnFileContents($file);
} catch (\Exception $e) {
Debugbar::addException($e);
$exception = 'An error occured while attempting to load the requested file for editing, please try again.';
if ($e instanceof DisplayException) {
$exception = $e->getMessage();
}
Alert::danger($exception)->flash();
return redirect()->route('files.index', $uuid);
}
return view('server.files.edit', [
'server' => $server,
'node' => Node::find($server->node),
'file' => $file,
'contents' => $fileContent->contents,
'directory' => (in_array($fileInfo->dirname, ['.', './', '/'])) ? '/' : trim($fileInfo->dirname, '/') . '/',
'extension' => $fileInfo->extension
]);
}
/**
* Handles downloading a file for the user.
*
* @param Request $request
* @param string $uuid
* @param string $file
* @return \Illuminate\Contracts\View\View
*/
public function getDownloadFile(Request $request, $uuid, $file)
{
$server = Server::getByUUID($uuid);
$node = Node::find($server->node);
$this->authorize('download-files', $server);
$download = new Download;
$download->token = Uuid::generate(4);
$download->server = $server->id;
$download->path = str_replace('../', '', $file);
$download->save();
return redirect('https://' . $node->fqdn . ':' . $node->daemonListen . '/server/download/' . $download->token);
}
}

58
app/Http/Helpers.php Normal file
View file

@ -0,0 +1,58 @@
<?php
namespace Pterodactyl\Http;
class Helpers {
/**
* Listing of editable files in the control panel.
* @var array
*/
protected static $editable = [
'txt',
'yml',
'yaml',
'log',
'conf',
'config',
'html',
'json',
'properties',
'props',
'cfg',
'lang',
'ini',
'cmd',
'sh',
'lua',
'0' // Supports BungeeCord Files
];
public function __construct()
{
//
}
/**
* Converts from bytes to the largest possible size that is still readable.
*
* @param int $bytes
* @param int $decimals
* @return string
*/
public static function bytesToHuman($bytes, $decimals = 2)
{
$sz = explode(',', 'B,KB,MB,GB');
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)).' '.$sz[$factor];
}
public static function editableFiles()
{
return self::$editable;
}
}

36
app/Http/Kernel.php Normal file
View file

@ -0,0 +1,36 @@
<?php
namespace Pterodactyl\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* @var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Pterodactyl\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Pterodactyl\Http\Middleware\VerifyCsrfToken::class,
];
/**
* The application's route middleware.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \Pterodactyl\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class,
'server' => \Pterodactyl\Http\MiddleWare\CheckServer::class,
'api' => \Pterodactyl\Http\MiddleWare\APIAuthenticate::class,
'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class,
];
}

View file

@ -0,0 +1,46 @@
<?php
namespace Pterodactyl\Http\Middleware;
use Closure;
use Debugbar;
use Pterodactyl\Models\API;
class APIAuthenticate
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if(!$request->header('X-Authorization')) {
return response()->json([
'error' => 'Authorization header was missing with this request. Please pass the \'X-Authorization\' header with your request.'
], 403);
}
$api = API::where('key', $request->header('X-Authorization'))->first();
if (!$api) {
return response()->json([
'error' => 'Invalid API key was provided in the request.'
], 403);
}
if (!is_null($api->allowed_ips)) {
if (!in_array($request->ip(), json_decode($api->allowed_ips, true))) {
return response()->json([
'error' => 'This IP (' . $request->ip() . ') is not permitted to access the API with that token.'
], 403);
}
}
return $next($request);
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Pterodactyl\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class AdminAuthenticate
{
/**
* The Guard implementation.
*
* @var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* @param Guard $auth
* @return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('auth/login');
}
}
if($this->auth->user()->root_admin !== 1) {
return abort(403);
}
return $next($request);
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Pterodactyl\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class Authenticate
{
/**
* The Guard implementation.
*
* @var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* @param Guard $auth
* @return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('auth/login');
}
}
return $next($request);
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Pterodactyl\Http\Middleware;
use Closure;
use Auth;
use Pterodactyl\Models\Server;
use Debugbar;
class CheckServer
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (!Auth::user()) {
return redirect()->guest('auth/login');
}
if (!Server::getByUUID($request->route()->server)) {
return redirect('/');
}
return $next($request);
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Pterodactyl\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as BaseEncrypter;
class EncryptCookies extends BaseEncrypter
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
//
];
}

View file

@ -0,0 +1,43 @@
<?php
namespace Pterodactyl\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class RedirectIfAuthenticated
{
/**
* The Guard implementation.
*
* @var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* @param Guard $auth
* @return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->check()) {
return redirect('/home');
}
return $next($request);
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Pterodactyl\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
//
];
}

View file

@ -0,0 +1,10 @@
<?php
namespace Pterodactyl\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
abstract class Request extends FormRequest
{
//
}

View file

@ -0,0 +1,30 @@
<?php
namespace Pterodactyl\Http\Routes;
use Illuminate\Routing\Router;
class AdminRoutes {
public function map(Router $router) {
$router->group(['prefix' => 'admin'], function ($server) use ($router) {
$router->get('/', [ 'as' => 'admin.index', 'uses' => 'Admin\BaseController@getIndex' ]);
// Account Routes
$router->group(['prefix' => 'accounts'], function ($server) use ($router) {
$router->get('/', [ 'as' => 'admin.accounts', 'uses' => 'Admin\AccountsController@getIndex' ]);
$router->get('/new', [ 'as' => 'admin.accounts.new', 'uses' => 'Admin\AccountsController@getNew' ]);
$router->get('/view/{id}', [ 'as' => 'admin.accounts.view', 'uses' => 'Admin\AccountsController@getView' ]);
});
// Server Routes
$router->group(['prefix' => 'servers'], function ($server) use ($router) {
$router->get('/', [ 'as' => 'admin.servers', 'uses' => 'Admin\ServersController@getIndex' ]);
$router->get('/new', [ 'as' => 'admin.servers.new', 'uses' => 'Admin\ServersController@getNew' ]);
$router->get('/view/{id}', [ 'as' => 'admin.servers.view', 'uses' => 'Admin\ServersController@getView' ]);
});
});
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Pterodactyl\Http\Routes;
use Illuminate\Routing\Router;
class AuthRoutes {
public function map(Router $router) {
$router->group(['prefix' => 'auth'], function () use ($router) {
$router->get('login', [ 'as' => 'auth.login', 'uses' => 'Auth\AuthController@getLogin' ]);
$router->post('login', [ 'as' => 'auth.login.submit', 'uses' => 'Auth\AuthController@postLogin' ]);
$router->get('logout', [ 'as' => 'auth.logout', 'uses' => 'Auth\AuthController@getLogout' ]);
});
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Pterodactyl\Http\Routes;
use Illuminate\Routing\Router;
class BaseRoutes {
public function map(Router $router) {
// Handle Index. Redirect /index to /
$router->get('/', [ 'as' => 'index', 'uses' => 'Base\IndexController@getIndex' ]);
$router->get('/index', function () {
return redirect()->route('index');
});
// Account Routes
$router->get('/account', [ 'as' => 'account', 'uses' => 'Base\IndexController@getAccount' ]);
$router->post('/account/password', [ 'uses' => 'Base\IndexController@postAccountPassword' ]);
$router->post('/account/email', [ 'uses' => 'Base\IndexController@postAccountEmail' ]);
// TOTP Routes
$router->get('/account/totp', [ 'as' => 'account.totp', 'uses' => 'Base\IndexController@getAccountTotp' ]);
$router->put('/account/totp', [ 'uses' => 'Base\IndexController@putAccountTotp' ]);
$router->post('/account/totp', [ 'uses' => 'Base\IndexController@postAccountTotp' ]);
$router->delete('/account/totp', [ 'uses' => 'Base\IndexController@deleteAccountTotp' ]);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Pterodactyl\Http\Routes;
use Illuminate\Routing\Router;
class RestRoutes {
public function map(Router $router) {
$router->group(['prefix' => 'api/v1'], function ($server) use ($router) {
$router->group(['prefix' => 'users'], function ($server) use ($router) {
$router->get('/', [ 'uses' => 'API\UserController@getAllUsers' ]);
$router->get('/{id}/{fields?}', [ 'uses' => 'API\UserController@getUser' ])->where('id', '[0-9]+');
});
});
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Pterodactyl\Http\Routes;
use Illuminate\Routing\Router;
class ServerRoutes {
public function map(Router $router) {
$router->group(['prefix' => 'server/{server}'], function ($server) use ($router) {
$router->get('/', [ 'as' => 'server.index', 'uses' => 'Server\ServerController@getIndex' ]);
$router->get('/files', [ 'as' => 'files.index', 'uses' => 'Server\ServerController@getFiles' ]);
$router->get('/files/edit/{file}', [ 'as' => 'files.edit', 'uses' => 'Server\ServerController@getEditFile' ])->where('file', '.*');
$router->get('/files/download/{file}', [ 'as' => 'files.download', 'uses' => 'Server\ServerController@getDownloadFile' ])->where('file', '.*');
$router->get('/files/add', [ 'as' => 'files.add', 'uses' => 'Server\ServerController@getAddFile' ]);
// Ajax Routes
$router->group(['prefix' => 'ajax'], function ($server) use ($router) {
$router->get('status', [ 'uses' => 'Server\AjaxController@getStatus' ]);
$router->post('files/directory-list', [ 'uses' => 'Server\AjaxController@postDirectoryList' ]);
$router->post('files/save', [ 'uses' => 'Server\AjaxController@postSaveFile' ]);
});
});
}
}

21
app/Jobs/Job.php Normal file
View file

@ -0,0 +1,21 @@
<?php
namespace Pterodactyl\Jobs;
use Illuminate\Bus\Queueable;
abstract class Job
{
/*
|--------------------------------------------------------------------------
| Queueable Jobs
|--------------------------------------------------------------------------
|
| This job base class provides a central location to place any logic that
| is shared across all of your jobs. The trait included with the class
| provides access to the "onQueue" and "delay" queue helper methods.
|
*/
use Queueable;
}

0
app/Listeners/.gitkeep Normal file
View file

63
app/Models/API.php Normal file
View file

@ -0,0 +1,63 @@
<?php
namespace Pterodactyl\Models;
use Log;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Models\APIPermission;
use Illuminate\Database\Eloquent\Model;
class API extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'api';
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = ['daemonSecret'];
public function permissions()
{
return $this->hasMany(APIPermission::class);
}
public static function findKey($key)
{
return self::where('key', $key)->first();
}
/**
* Determine if an API key has permission to perform an action.
*
* @param string $key
* @param string $permission
* @return boolean
*/
public static function checkPermission($key, $permission)
{
$api = self::findKey($key);
if (!$api) {
throw new DisplayException('The requested API key (' . $key . ') was not found in the system.');
}
return APIPermission::check($api->id, $permission);
}
public static function noPermissionError($error = 'You do not have permission to perform this action with this API key.')
{
return response()->json([
'error' => 'You do not have permission to perform this action with this API key.'
], 403);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Pterodactyl\Models;
use Debugbar;
use Illuminate\Database\Eloquent\Model;
class APIPermission extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'api_permissions';
/**
* Checks if an API key has a specific permission.
*
* @param int $id
* @param string $permission
* @return boolean
*/
public static function check($id, $permission)
{
return self::where('key_id', $id)->where('permission', $permission)->exists();
}
}

18
app/Models/Download.php Normal file
View file

@ -0,0 +1,18 @@
<?php
namespace Pterodactyl\Models;
use Debugbar;
use Illuminate\Database\Eloquent\Model;
class Download extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'downloads';
}

82
app/Models/Node.php Normal file
View file

@ -0,0 +1,82 @@
<?php
namespace Pterodactyl\Models;
use GuzzleHttp\Client;
use Illuminate\Database\Eloquent\Model;
class Node extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'nodes';
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = ['daemonSecret'];
/**
* @var array
*/
protected static $guzzle = [];
/**
* @var array
*/
protected static $nodes = [];
/**
* Returns an instance of the database object for the requested node ID.
*
* @param int $id
* @return \Illuminate\Database\Eloquent\Model
*/
public static function getByID($id)
{
// The Node is already cached.
if (array_key_exists($id, self::$nodes)) {
return self::$nodes[$id];
}
self::$nodes[$id] = Node::where('id', $id)->first();
return self::$nodes[$id];
}
/**
* Returns a Guzzle Client for the node in question.
*
* @param int $node
* @return \GuzzleHttp\Client
*/
public static function guzzleRequest($node)
{
// The Guzzle Client is cached already.
if (array_key_exists($node, self::$guzzle)) {
return self::$guzzle[$node];
}
$nodeData = self::getByID($node);
// @TODO: Better solution to disabling verification. Security risk.
self::$guzzle[$node] = new Client([
'base_uri' => sprintf('https://%s:%s/', $nodeData->fqdn, $nodeData->daemonListen),
'timeout' => 10.0,
'connect_timeout' => 5.0,
'verify' => false,
]);
return self::$guzzle[$node];
}
}

27
app/Models/Permission.php Normal file
View file

@ -0,0 +1,27 @@
<?php
namespace Pterodactyl\Models;
use Illuminate\Database\Eloquent\Model;
class Permission extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'permissions';
public function scopePermission($query, $permission)
{
return $query->where('permission', $permission);
}
public function scopeServer($query, $server)
{
return $query->where('server_id', $server->id);
}
}

141
app/Models/Server.php Normal file
View file

@ -0,0 +1,141 @@
<?php
namespace Pterodactyl\Models;
use Auth;
use Pterodactyl\Models\Permission;
use Pterodactyl\Models\Subuser;
use Illuminate\Database\Eloquent\Model;
class Server extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'servers';
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = ['daemonSecret'];
/**
* @var array
*/
protected static $serverUUIDInstance = [];
/**
* @var mixed
*/
protected static $user;
/**
* Constructor
*/
public function __construct()
{
self::$user = Auth::user();
}
/**
* Determine if we need to change the server's daemonSecret value to
* match that of the user if they are a subuser.
*
* @param Illuminate\Database\Eloquent\Model\Server $server
* @return string
*/
protected static function getUserDaemonSecret(Server $server)
{
if (self::$user->id === $server->owner || self::$user->root_admin === 1) {
return $server->daemonSecret;
}
$subuser = Subuser::where('server_id', $server->id)->where('user_id', self::$user->id)->first();
if (is_null($subuser)) {
return null;
}
return $subuser->daemonSecret;
}
/**
* Returns array of all servers owned by the logged in user.
* Returns all active servers if user is a root admin.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getUserServers()
{
$query = self::select('servers.*', 'nodes.name as nodeName', 'locations.long as location')
->join('nodes', 'servers.node', '=', 'nodes.id')
->join('locations', 'nodes.location', '=', 'locations.id')
->where('active', 1);
if (self::$user->root_admin !== 1) {
$query->whereIn('servers.id', Subuser::accessServers());
}
return $query->get();
}
/**
* Returns a single server specified by UUID
*
* @param string $uuid The Short-UUID of the server to return an object about.
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getByUUID($uuid)
{
if (array_key_exists($uuid, self::$serverUUIDInstance)) {
return self::$serverUUIDInstance[$uuid];
}
$query = self::where('uuidShort', $uuid)->where('active', 1);
if (self::$user->root_admin !== 1) {
$query->whereIn('servers.id', Subuser::accessServers());
}
$result = $query->first();
if(!is_null($result)) {
$result->daemonSecret = self::getUserDaemonSecret($result);
}
self::$serverUUIDInstance[$uuid] = $result;
return self::$serverUUIDInstance[$uuid];
}
/**
* Returns non-administrative headers for accessing a server on Scales
*
* @param string $uuid
* @return array
*/
public static function getGuzzleHeaders($uuid)
{
if (array_key_exists($uuid, self::$serverUUIDInstance)) {
return [
'X-Access-Server' => self::$serverUUIDInstance[$uuid]->uuid,
'X-Access-Token' => self::$serverUUIDInstance[$uuid]->daemonSecret
];
}
return [];
}
}

52
app/Models/Subuser.php Normal file
View file

@ -0,0 +1,52 @@
<?php
namespace Pterodactyl\Models;
use Auth;
use Illuminate\Database\Eloquent\Model;
class Subuser extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'subusers';
/**
* @var mixed
*/
protected static $user;
/**
* Constructor
*/
public function __construct()
{
self::$user = Auth::user();
}
/**
* Returns an array of each server ID that the user has access to.
*
* @return array
*/
public static function accessServers()
{
$access = [];
$union = self::select('server_id')->where('user_id', self::$user->id);
$select = Server::select('id')->where('owner', self::$user->id)->union($union)->get();
foreach($select as &$select) {
$access = array_merge($access, [ $select->id ]);
}
return $access;
}
}

157
app/Models/User.php Normal file
View file

@ -0,0 +1,157 @@
<?php
namespace Pterodactyl\Models;
use Google2FA;
use Pterodactyl\Exceptions\AccountNotFoundException;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Models\Permission;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends Model implements AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'users';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name', 'email', 'password'];
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = ['password', 'remember_token', 'totp_secret'];
public function permissions()
{
return $this->hasMany(Permission::class);
}
/**
* Sets the TOTP secret for an account.
*
* @param int $id Account ID for which we want to generate a TOTP secret
* @return string
*/
public static function setTotpSecret($id)
{
$totpSecretKey = Google2FA::generateSecretKey();
$user = User::find($id);
if (!$user) {
throw new AccountNotFoundException('An account with that ID (' . $id . ') does not exist in the system.');
}
$user->totp_secret = $totpSecretKey;
$user->save();
return $totpSecretKey;
}
/**
* Enables or disables TOTP on an account if the token is valid.
*
* @param int $id Account ID for which we want to generate a TOTP secret
* @return boolean
*/
public static function toggleTotp($id, $token)
{
$user = User::find($id);
if (!$user) {
throw new AccountNotFoundException('An account with that ID (' . $id . ') does not exist in the system.');
}
if (!Google2FA::verifyKey($user->totp_secret, $token)) {
return false;
}
$user->use_totp = ($user->use_totp === 1) ? 0 : 1;
$user->save();
return true;
}
/**
* Set a user password to a new value assuming it meets the following requirements:
* - 8 or more characters in length
* - at least one uppercase character
* - at least one lowercase character
* - at least one number
*
* @param int $id The ID of the account to update the password on.
* @param string $password The raw password to set the account password to.
* @param string $regex The regex to use when validating the password. Defaults to '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'.
* @return void
*/
public static function setPassword($id, $password, $regex = '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})')
{
$user = User::find($id);
if (!$user) {
throw new AccountNotFoundException('An account with that ID (' . $id . ') does not exist in the system.');
}
if (!preg_match($regex, $password)) {
throw new DisplayException('The password passed did not meet the minimum password requirements.');
}
$user->password = password_hash($password, PASSWORD_BCRYPT);
$user->save();
return;
}
/**
* Updates the email address for an account.
*
* @param int $id
* @param string $email
* @return void
*/
public static function setEmail($id, $email)
{
$user = User::find($id);
if (!$user) {
throw new AccountNotFoundException('An account with that ID (' . $id . ') does not exist in the system.');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new DisplayException('The email provided (' . $email . ') was not valid.');
}
$user->email = $email;
$user->save();
return;
}
}

0
app/Policies/.gitkeep Normal file
View file

View file

@ -0,0 +1,178 @@
<?php
namespace Pterodactyl\Policies;
use Log;
use Debugbar;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
class ServerPolicy
{
/**
* Create a new policy instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Determine if current user is the owner of a server.
*
* @param Pterodactyl\Models\User $user
* @param Pterodactyl\Models\Server $server
* @return boolean
*/
protected function isOwner(User $user, Server $server)
{
return $server->owner === $user->id;
}
/**
* Runs before any of the functions are called. Used to determine if user is root admin, if so, ignore permissions.
*
* @param Pterodactyl\Models\User $user
* @param string $ability
* @return boolean
*/
public function before(User $user, $ability)
{
if ($user->root_admin === 1) {
return true;
}
}
/**
* Check if user has permission to control power for a server.
*
* @param Pterodactyl\Models\User $user
* @param Pterodactyl\Models\Server $server
* @return boolean
*/
public function power(User $user, Server $server)
{
if ($this->isOwner($user, $server)) {
return true;
}
return $user->permissions()->server($server)->permission('power')->exists();
}
/**
* Check if user has permission to run a command on a server.
*
* @param Pterodactyl\Models\User $user
* @param Pterodactyl\Models\Server $server
* @return boolean
*/
public function command(User $user, Server $server)
{
if ($this->isOwner($user, $server)) {
return true;
}
return $user->permissions()->server($server)->permission('command')->exists();
}
/**
* Check if user has permission to list files on a server.
*
* @param Pterodactyl\Models\User $user
* @param Pterodactyl\Models\Server $server
* @return boolean
*/
public function listFiles(User $user, Server $server)
{
if ($this->isOwner($user, $server)) {
return true;
}
return $user->permissions()->server($server)->permission('list-files')->exists();
}
/**
* Check if user has permission to edit files on a server.
*
* @param Pterodactyl\Models\User $user
* @param Pterodactyl\Models\Server $server
* @return boolean
*/
public function editFiles(User $user, Server $server)
{
if ($this->isOwner($user, $server)) {
return true;
}
return $user->permissions()->server($server)->permission('edit-files')->exists();
}
/**
* Check if user has permission to save files on a server.
*
* @param Pterodactyl\Models\User $user
* @param Pterodactyl\Models\Server $server
* @return boolean
*/
public function saveFiles(User $user, Server $server)
{
if ($this->isOwner($user, $server)) {
return true;
}
return $user->permissions()->server($server)->permission('save-files')->exists();
}
/**
* Check if user has permission to add files to a server.
*
* @param Pterodactyl\Models\User $user
* @param Pterodactyl\Models\Server $server
* @return boolean
*/
public function addFiles(User $user, Server $server)
{
if ($this->isOwner($user, $server)) {
return true;
}
return $user->permissions()->server($server)->permission('add-files')->exists();
}
/**
* Check if user has permission to upload files to a server.
* This permission relies on the user having the 'add-files' permission as well due to page authorization.
*
* @param Pterodactyl\Models\User $user
* @param Pterodactyl\Models\Server $server
* @return boolean
*/
public function uploadFiles(User $user, Server $server)
{
if ($this->isOwner($user, $server)) {
return true;
}
return $user->permissions()->server($server)->permission('upload-files')->exists();
}
/**
* Check if user has permission to download files from a server.
*
* @param Pterodactyl\Models\User $user
* @param Pterodactyl\Models\Server $server
* @return boolean
*/
public function downloadFiles(User $user, Server $server)
{
if ($this->isOwner($user, $server)) {
return true;
}
return $user->permissions()->server($server)->permission('download-files')->exists();
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Pterodactyl\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Pterodactyl\Providers;
use Pterodactyl\Models\Server;
use Pterodactyl\Policies\ServerPolicy;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'Pterodactyl\Model' => 'Pterodactyl\Policies\ModelPolicy',
Server::class => ServerPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
parent::registerPolicies($gate);
//
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Pterodactyl\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
'Pterodactyl\Events\SomeEvent' => [
'Pterodactyl\Listeners\EventListener',
],
];
/**
* Register any other events for your application.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function boot(DispatcherContract $events)
{
parent::boot($events);
//
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Pterodactyl\Providers;
use Illuminate\Routing\Router;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to the controller routes in your routes file.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'Pterodactyl\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function boot(Router $router)
{
//
parent::boot($router);
}
/**
* Define the routes for the application.
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function map(Router $router)
{
$router->group(['namespace' => $this->namespace], function ($router) {
foreach (glob(app_path('Http//Routes') . '/*.php') as $file) {
$this->app->make('Pterodactyl\\Http\\Routes\\' . basename($file, '.php'))->map($router);
}
});
}
}