diff --git a/CHANGELOG.md b/CHANGELOG.md index 54484b889..104256f65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v0.7.2 (Derelict Dermodactylus) +### Fixed +* Fixes an exception thrown when trying to access the `/nests/:id/eggs/:id` API endpoint. +* Fixes search on server listing page. +* Schedules with no names are now clickable to allow editing. +* Fixes broken permissions check that would deny access to API keys that did in fact have permission. + +### Added +* Adds ability to include egg variables on an API request. +* Added `external_id` column to servers that allows for easier linking with external services such as WHMCS. +* Added back the sidebar when viewing servers that allows for quick-switching to a different server. +* Added API endpoint to get a server by external ID. + ## v0.7.1 (Derelict Dermodactylus) ### Fixed * Fixes an exception when no token is entered on the 2-Factor enable/disable page and the form is submitted. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f2512847..8f701c8a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,4 +41,4 @@ If you've found what you believe is a security issue please email us at `support ### Where to find Us You can find us in a couple places online. First and foremost, we're active right here on Github. If you encounter a bug or other problem open an issue on here for us to take a look at it. We also accept feature requests here as well. -You can also find us on [Discord](https://pterodactyl.io/discord). In the event that you need to get in contact with us privately feel free to contact us at `support@pterodactyl.io`. Try not to email us with requests for support regarding the panel, we'll probably just direct you to our forums or Discord. +You can also find us on [Discord](https://pterodactyl.io/discord). In the event that you need to get in contact with us privately feel free to contact us at `support@pterodactyl.io`. Try not to email us with requests for support regarding the panel, we'll probably just direct you to our Discord. diff --git a/README.md b/README.md index c74fa7945..358e74fa5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Pterodactyl Panel is the free, open-source, game agnostic, self-hosted control panel for users, networks, and game service providers. Pterodactyl supports games and servers such as Minecraft (including Spigot, Bungeecord, and Sponge), ARK: Evolution Evolved, CS:GO, Team Fortress 2, Insurgency, Teamspeak 3, Mumble, and many more. Control all of your games from one unified interface. ## Support & Documentation -Support for using Pterodactyl can be found on our [Documentation Website](https://docs.pterodactyl.io), our [Discord Chat](https://discord.gg/QRDZvVm), or via our [Forums](https://forums.pterodactyl.io). +Support for using Pterodactyl can be found on our [Documentation Website](https://docs.pterodactyl.io) or via our [Discord Chat](https://discord.gg/QRDZvVm). ## License ``` diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php index 6d1c14e78..0ca74bf40 100644 --- a/app/Contracts/Repository/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/ServerRepositoryInterface.php @@ -103,9 +103,10 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter * * @param \Pterodactyl\Models\User $user * @param int $level - * @return \Illuminate\Pagination\LengthAwarePaginator + * @param bool $paginate + * @return \Illuminate\Pagination\LengthAwarePaginator|\Illuminate\Database\Eloquent\Collection */ - public function filterUserAccessServers(User $user, int $level): LengthAwarePaginator; + public function filterUserAccessServers(User $user, int $level, bool $paginate = true); /** * Return a server by UUID. diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index c64e76711..6b9d44cbb 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -201,12 +201,13 @@ class ServersController extends Controller /** * Display the index page with all servers currently on the system. * + * @param \Illuminate\Http\Request $request * @return \Illuminate\View\View */ - public function index() + public function index(Request $request) { return view('admin.servers.index', [ - 'servers' => $this->repository->getAllServers( + 'servers' => $this->repository->setSearchTerm($request->input('query'))->getAllServers( $this->config->get('pterodactyl.paginate.admin.servers') ), ]); @@ -405,7 +406,7 @@ class ServersController extends Controller public function setDetails(Request $request, Server $server) { $this->detailsModificationService->handle($server, $request->only([ - 'owner_id', 'name', 'description', + 'owner_id', 'external_id', 'name', 'description', ])); $this->alert->success(trans('admin/server.alerts.details_updated'))->flash(); diff --git a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php new file mode 100644 index 000000000..391c5645c --- /dev/null +++ b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php @@ -0,0 +1,23 @@ +fractal->item($request->getServerModel()) + ->transformWith($this->getTransformer(ServerTransformer::class)) + ->toArray(); + } +} diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index f869393af..1ef1e0b62 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -9,6 +9,7 @@ use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerDeletionService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Transformers\Api\Application\ServerTransformer; +use Pterodactyl\Http\Requests\Api\Application\Servers\GetServerRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest; @@ -91,10 +92,10 @@ class ServerController extends ApplicationApiController /** * Show a single server transformed for the application API. * - * @param \Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest $request + * @param \Pterodactyl\Http\Requests\Api\Application\Servers\GetServerRequest $request * @return array */ - public function view(ServerWriteRequest $request): array + public function view(GetServerRequest $request): array { return $this->fractal->item($request->getModel(Server::class)) ->transformWith($this->getTransformer(ServerTransformer::class)) diff --git a/app/Http/Middleware/Api/Application/AuthenticateUser.php b/app/Http/Middleware/Api/Application/AuthenticateUser.php index 5bbce8296..7208dbaf9 100644 --- a/app/Http/Middleware/Api/Application/AuthenticateUser.php +++ b/app/Http/Middleware/Api/Application/AuthenticateUser.php @@ -19,7 +19,7 @@ class AuthenticateUser public function handle(Request $request, Closure $next) { if (is_null($request->user()) || ! $request->user()->root_admin) { - throw new AccessDeniedHttpException; + throw new AccessDeniedHttpException('This account does not have permission to access the API.'); } return $next($request); diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index 126e6d604..ada9b4b00 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -9,6 +9,7 @@ use Illuminate\Foundation\Http\FormRequest; use Pterodactyl\Exceptions\PterodactylException; use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Routing\Exception\InvalidParameterException; abstract class ApplicationApiRequest extends FormRequest { @@ -76,22 +77,23 @@ abstract class ApplicationApiRequest extends FormRequest } /** - * Grab a model from the route parameters. If no model exists under - * the specified key a default response is returned. + * Grab a model from the route parameters. If no model is found in the + * binding mappings an exception will be thrown. * * @param string $model - * @param mixed $default * @return mixed + * + * @throws \Symfony\Component\Routing\Exception\InvalidParameterException */ - public function getModel(string $model, $default = null) + public function getModel(string $model) { $parameterKey = array_get(array_flip(ApiSubstituteBindings::getMappings()), $model); - if (! is_null($parameterKey)) { - $model = $this->route()->parameter($parameterKey); + if (is_null($parameterKey)) { + throw new InvalidParameterException; } - return $model ?? $default; + return $this->route()->parameter($parameterKey); } /* diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php index b3f1a08a0..fb364d1ae 100644 --- a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php +++ b/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php @@ -2,6 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs; +use Pterodactyl\Models\Egg; +use Pterodactyl\Models\Nest; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; @@ -24,6 +26,6 @@ class GetEggRequest extends ApplicationApiRequest */ public function resourceExists(): bool { - return $this->getModel('nest')->id === $this->getModel('egg')->nest_id; + return $this->getModel(Nest::class)->id === $this->getModel(Egg::class)->nest_id; } } diff --git a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php new file mode 100644 index 000000000..f783dc715 --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php @@ -0,0 +1,57 @@ +container->make(ServerRepositoryInterface::class); + + try { + $this->serverModel = $repository->findFirstWhere([ + ['external_id', '=', $this->route()->parameter('external_id')], + ]); + } catch (RecordNotFoundException $exception) { + return false; + } + + return true; + } + + /** + * Return the server model for the requested external server. + * + * @return \Pterodactyl\Models\Server + */ + public function getServerModel(): Server + { + return $this->serverModel; + } +} diff --git a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php new file mode 100644 index 000000000..82d12687c --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php @@ -0,0 +1,19 @@ + $rules['external_id'], 'name' => $rules['name'], 'description' => array_merge(['nullable'], $rules['description']), 'user' => $rules['owner_id'], diff --git a/app/Http/Requests/Server/ScheduleCreationFormRequest.php b/app/Http/Requests/Server/ScheduleCreationFormRequest.php index 2be18434f..a90dd749a 100644 --- a/app/Http/Requests/Server/ScheduleCreationFormRequest.php +++ b/app/Http/Requests/Server/ScheduleCreationFormRequest.php @@ -33,7 +33,7 @@ class ScheduleCreationFormRequest extends ServerFormRequest public function rules() { return [ - 'name' => 'string|max:255', + 'name' => 'nullable|string|max:255', 'cron_day_of_week' => 'required|string', 'cron_day_of_month' => 'required|string', 'cron_hour' => 'required|string', diff --git a/app/Http/ViewComposers/ServerListComposer.php b/app/Http/ViewComposers/ServerListComposer.php new file mode 100644 index 000000000..4c3ac71fb --- /dev/null +++ b/app/Http/ViewComposers/ServerListComposer.php @@ -0,0 +1,51 @@ +request = $request; + $this->repository = $repository; + } + + /** + * Attach a list of servers the user can access to the view. + * + * @param \Illuminate\View\View $view + */ + public function compose(View $view) + { + if (! $this->request->user()) { + return; + } + + $servers = $this->repository + ->setColumns(['id', 'owner_id', 'uuidShort', 'name', 'description']) + ->filterUserAccessServers($this->request->user(), User::FILTER_LEVEL_SUBUSER, false); + + $view->with('sidebarServerList', $servers); + } +} diff --git a/app/Models/Node.php b/app/Models/Node.php index d5d5ac9bd..0134b7c7a 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -157,6 +157,17 @@ class Node extends Model implements CleansAttributes, ValidableContract 'filesystem' => [ 'server_logs' => '/tmp/pterodactyl', ], + 'internals' => [ + 'disk_use_seconds' => 30, + 'set_permissions_on_boot' => true, + 'throttle' => [ + 'enabled' => true, + 'kill_at_count' => 5, + 'decay' => 10, + 'bytes' => 30720, + 'check_interval_ms' => 100, + ], + ], 'sftp' => [ 'path' => $this->daemonBase, 'ip' => '0.0.0.0', diff --git a/app/Models/Server.php b/app/Models/Server.php index f10e52b1d..458bff1d6 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -53,6 +53,7 @@ class Server extends Model implements CleansAttributes, ValidableContract * @var array */ protected static $applicationRules = [ + 'external_id' => 'sometimes', 'owner_id' => 'required', 'name' => 'required', 'memory' => 'required', @@ -74,6 +75,7 @@ class Server extends Model implements CleansAttributes, ValidableContract * @var array */ protected static $dataIntegrityRules = [ + 'external_id' => 'nullable|string|between:1,191|unique:servers', 'owner_id' => 'integer|exists:users,id', 'name' => 'string|min:1|max:255', 'node_id' => 'exists:nodes,id', @@ -122,13 +124,14 @@ class Server extends Model implements CleansAttributes, ValidableContract * @var array */ protected $searchableColumns = [ - 'name' => 50, - 'uuidShort' => 10, - 'uuid' => 10, - 'pack.name' => 5, - 'user.email' => 20, - 'user.username' => 20, + 'name' => 100, + 'uuid' => 80, + 'uuidShort' => 80, + 'external_id' => 50, + 'user.email' => 40, + 'user.username' => 30, 'node.name' => 10, + 'pack.name' => 10, ]; /** diff --git a/app/Models/User.php b/app/Models/User.php index 88960994f..10c710f19 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -64,6 +64,7 @@ class User extends Model implements * @var array */ protected $fillable = [ + 'external_id', 'username', 'email', 'name_first', diff --git a/app/Providers/ViewComposerServiceProvider.php b/app/Providers/ViewComposerServiceProvider.php index dddc925f4..ab8c9e164 100644 --- a/app/Providers/ViewComposerServiceProvider.php +++ b/app/Providers/ViewComposerServiceProvider.php @@ -1,15 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Providers; use Illuminate\Support\ServiceProvider; +use Pterodactyl\Http\ViewComposers\ServerListComposer; use Pterodactyl\Http\ViewComposers\Server\ServerDataComposer; class ViewComposerServiceProvider extends ServiceProvider @@ -20,5 +14,8 @@ class ViewComposerServiceProvider extends ServiceProvider public function boot() { $this->app->make('view')->composer('server.*', ServerDataComposer::class); + + // Add data to make the sidebar work when viewing a server. + $this->app->make('view')->composer(['server.*'], ServerListComposer::class); } } diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index 4bf058a72..7bca12691 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -211,11 +211,12 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt * * @param \Pterodactyl\Models\User $user * @param int $level - * @return \Illuminate\Pagination\LengthAwarePaginator + * @param bool $paginate + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|\Illuminate\Database\Eloquent\Collection */ - public function filterUserAccessServers(User $user, int $level): LengthAwarePaginator + public function filterUserAccessServers(User $user, int $level, bool $paginate = true) { - $instance = $this->getBuilder()->with(['user']); + $instance = $this->getBuilder()->select($this->getColumns())->with(['user']); // If access level is set to owner, only display servers // that the user owns. @@ -224,8 +225,9 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt } // If set to all, display all servers they can access, including - // those they access as an admin. If set to subuser, only return the servers they can access because - // they are owner, or marked as a subuser of the server. + // those they access as an admin. If set to subuser, only return + // the servers they can access because they are owner, or marked + // as a subuser of the server. elseif (($level === User::FILTER_LEVEL_ALL && ! $user->root_admin) || $level === User::FILTER_LEVEL_SUBUSER) { $instance->whereIn('id', $this->getUserAccessServers($user->id)); } @@ -236,7 +238,9 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt $instance->whereNotIn('id', $this->getUserAccessServers($user->id)); } - return $instance->search($this->getSearchTerm())->paginate(25); + $instance->search($this->getSearchTerm()); + + return $paginate ? $instance->paginate(25) : $instance->get(); } /** diff --git a/app/Services/Servers/DetailsModificationService.php b/app/Services/Servers/DetailsModificationService.php index 78d8eb31e..523185f2e 100644 --- a/app/Services/Servers/DetailsModificationService.php +++ b/app/Services/Servers/DetailsModificationService.php @@ -69,6 +69,7 @@ class DetailsModificationService $this->connection->beginTransaction(); $response = $this->repository->setFreshModel($this->getUpdatedModel())->update($server->id, [ + 'external_id' => array_get($data, 'external_id'), 'owner_id' => array_get($data, 'owner_id'), 'name' => array_get($data, 'name'), 'description' => array_get($data, 'description') ?? '', diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index a57e7712b..0c5ecd0d4 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -211,6 +211,7 @@ class ServerCreationService private function createModel(array $data): Server { return $this->repository->create([ + 'external_id' => array_get($data, 'external_id'), 'uuid' => Uuid::uuid4()->toString(), 'uuidShort' => str_random(8), 'node_id' => array_get($data, 'node_id'), diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index 3454547bd..b73500bb8 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Pterodactyl\Models\Server; +use Pterodactyl\Models\EggVariable; use Pterodactyl\Services\Acl\Api\AdminAcl; class EggTransformer extends BaseTransformer @@ -15,7 +16,7 @@ class EggTransformer extends BaseTransformer * @var array */ protected $availableIncludes = [ - 'nest', 'servers', 'config', 'script', + 'nest', 'servers', 'config', 'script', 'variables', ]; /** @@ -147,4 +148,25 @@ class EggTransformer extends BaseTransformer ]; }); } + + /** + * Include the variables that are defined for this Egg. + * + * @param \Pterodactyl\Models\Egg $model + * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource + */ + public function includeVariables(Egg $model) + { + if (! $this->authorize(AdminAcl::RESOURCE_EGGS)) { + return $this->null(); + } + + $model->loadMissing('variables'); + + return $this->collection( + $model->getRelation('variables'), + $this->makeTransformer(EggVariableTransformer::class), + EggVariable::RESOURCE_NAME + ); + } } diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index 914449ef2..69115d1ed 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Transformers\Api\Application; -use Cake\Chronos\Chronos; use Pterodactyl\Models\Server; use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Servers\EnvironmentService; @@ -63,6 +62,7 @@ class ServerTransformer extends BaseTransformer { return [ 'id' => $server->getKey(), + 'external_id' => $server->external_id, 'uuid' => $server->uuid, 'identifier' => $server->uuidShort, 'name' => $server->name, @@ -87,8 +87,8 @@ class ServerTransformer extends BaseTransformer 'installed' => (int) $server->installed === 1, 'environment' => $this->environmentService->handle($server), ], - 'created_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $server->created_at)->setTimezone('UTC')->toIso8601String(), - 'updated_at' => Chronos::createFromFormat(Chronos::DEFAULT_TO_STRING_FORMAT, $server->updated_at)->setTimezone('UTC')->toIso8601String(), + $server->getUpdatedAtColumn() => $this->formatTimestamp($server->updated_at), + $server->getCreatedAtColumn() => $this->formatTimestamp($server->created_at), ]; } diff --git a/config/app.php b/config/app.php index b1f233eb6..bb4b66015 100644 --- a/config/app.php +++ b/config/app.php @@ -9,7 +9,7 @@ return [ | change this value if you are not maintaining your own internal versions. */ - 'version' => '0.7.1', + 'version' => '0.7.2', /* |-------------------------------------------------------------------------- diff --git a/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php b/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php new file mode 100644 index 000000000..2c8af99e2 --- /dev/null +++ b/database/migrations/2018_02_24_112356_add_external_id_column_to_servers_table.php @@ -0,0 +1,32 @@ +string('external_id')->after('id')->nullable()->unique(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('external_id'); + }); + } +} diff --git a/public/themes/pterodactyl/js/frontend/console.js b/public/themes/pterodactyl/js/frontend/console.js index 97bd73854..8943da42a 100644 --- a/public/themes/pterodactyl/js/frontend/console.js +++ b/public/themes/pterodactyl/js/frontend/console.js @@ -151,14 +151,14 @@ function pushToTerminal(string) { if (TerminalQueue.length > 0) { var scrolledDown = isTerminalScrolledDown(); - + for (var i = 0; i < CONSOLE_PUSH_COUNT && TerminalQueue.length > 0; i++) { pushToTerminal(TerminalQueue[0]); window.ConsoleElements++; TerminalQueue.shift(); } - + if (scrolledDown) { window.scrollToBottom(); } else if ($scrollNotify.hasClass('hidden')) { diff --git a/resources/themes/pterodactyl/admin/servers/view/details.blade.php b/resources/themes/pterodactyl/admin/servers/view/details.blade.php index 60bcded6d..bd4a5aef2 100644 --- a/resources/themes/pterodactyl/admin/servers/view/details.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/details.blade.php @@ -47,12 +47,17 @@