diff --git a/.env.travis b/.env.travis index 22a0c3047..f1d4f5698 100644 --- a/.env.travis +++ b/.env.travis @@ -16,3 +16,4 @@ MAIL_DRIVER=array QUEUE_DRIVER=sync HASHIDS_SALT=test123 +APP_ENVIRONMENT_ONLY=true diff --git a/.php_cs b/.php_cs index c2bbfe8c6..c854af47c 100644 --- a/.php_cs +++ b/.php_cs @@ -51,5 +51,5 @@ return PhpCsFixer\Config::create() 'equal' => false, 'identical' => false, 'less_and_greater' => false, - ] + ], ])->setRiskyAllowed(true)->setFinder($finder); diff --git a/.travis.yml b/.travis.yml index 277c25df2..d07b2cbfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,13 @@ language: php dist: trusty php: - - '7.0' - - '7.1' -# - '7.2' + - 7.0 + - 7.1 + - 7.2 +matrix: + fast_finish: true + allow_failures: + - php: 7.2 sudo: false cache: directories: diff --git a/CHANGELOG.md b/CHANGELOG.md index 825277fb7..23ecaeb0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,17 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. * `[beta.2]` — Fixes a bug that would throw a red page of death when submitting an invalid egg variable value for a server in the Admin CP. * `[beta.2]` — Someone found a `@todo` that I never `@todid` and thus database hosts could not be created without being linked to a node. This is fixed... * `[beta.2]` — Fixes bug that caused incorrect rendering of CPU usage on server graphs due to missing variable. +* `[beta.2]` — Fixes bug causing schedules to be un-deletable. +* `[beta.2]` — Fixes bug that prevented the deletion of nodes due to an allocation deletion cascade issue with the SQL schema. + +### Changed +* Revoking the administrative status for an admin will revoke all authentication tokens currently assigned to their account. + +### Added +* Added star indicators to user listing in Admin CP to indicate users who are set as a root admin. + +### Changed +* API keys have been changed to only use a single public key passed in a bearer token. All existing keys can continue being used, however only the first 32 characters should be sent. ### Changed * API keys have been changed to only use a single public key passed in a bearer token. All existing keys can continue being used, however only the first 32 characters should be sent. diff --git a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php index 6b7a86d45..19806ac49 100644 --- a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php @@ -78,8 +78,10 @@ interface ServerRepositoryInterface extends BaseRepositoryInterface /** * Revoke an access key on the daemon before the time is expired. * - * @param string $key + * @param string|array $key * @return \Psr\Http\Message\ResponseInterface + * + * @throws \GuzzleHttp\Exception\RequestException */ public function revokeAccessKey($key); } diff --git a/app/Contracts/Repository/DaemonKeyRepositoryInterface.php b/app/Contracts/Repository/DaemonKeyRepositoryInterface.php index 572f18c24..4f2156ad5 100644 --- a/app/Contracts/Repository/DaemonKeyRepositoryInterface.php +++ b/app/Contracts/Repository/DaemonKeyRepositoryInterface.php @@ -24,7 +24,9 @@ namespace Pterodactyl\Contracts\Repository; +use Pterodactyl\Models\User; use Pterodactyl\Models\DaemonKey; +use Illuminate\Support\Collection; interface DaemonKeyRepositoryInterface extends RepositoryInterface { @@ -59,4 +61,22 @@ interface DaemonKeyRepositoryInterface extends RepositoryInterface * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function getKeyWithServer($key); + + /** + * Get all of the keys for a specific user including the information needed + * from their server relation for revocation on the daemon. + * + * @param \Pterodactyl\Models\User $user + * @return \Illuminate\Support\Collection + */ + public function getKeysForRevocation(User $user): Collection; + + /** + * Delete an array of daemon keys from the database. Used primarily in + * conjunction with getKeysForRevocation. + * + * @param array $ids + * @return bool|int + */ + public function deleteKeys(array $ids); } diff --git a/app/Contracts/Repository/SettingsRepositoryInterface.php b/app/Contracts/Repository/SettingsRepositoryInterface.php new file mode 100644 index 000000000..dbf87f744 --- /dev/null +++ b/app/Contracts/Repository/SettingsRepositoryInterface.php @@ -0,0 +1,32 @@ +. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Http\Connection; diff --git a/app/Http/Controllers/Admin/BaseController.php b/app/Http/Controllers/Admin/BaseController.php index ebdebd361..b80676f2e 100644 --- a/app/Http/Controllers/Admin/BaseController.php +++ b/app/Http/Controllers/Admin/BaseController.php @@ -1,51 +1,25 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin; -use Krucas\Settings\Settings; -use Prologue\Alerts\AlertsMessageBag; +use Illuminate\View\View; use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Http\Requests\Admin\BaseFormRequest; use Pterodactyl\Services\Helpers\SoftwareVersionService; class BaseController extends Controller { - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Krucas\Settings\Settings - */ - protected $settings; - /** * @var \Pterodactyl\Services\Helpers\SoftwareVersionService */ - protected $version; + private $version; /** * BaseController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Krucas\Settings\Settings $settings * @param \Pterodactyl\Services\Helpers\SoftwareVersionService $version */ - public function __construct( - AlertsMessageBag $alert, - Settings $settings, - SoftwareVersionService $version - ) { - $this->alert = $alert; - $this->settings = $settings; + public function __construct(SoftwareVersionService $version) + { $this->version = $version; } @@ -54,34 +28,8 @@ class BaseController extends Controller * * @return \Illuminate\View\View */ - public function getIndex() + public function index(): View { return view('admin.index', ['version' => $this->version]); } - - /** - * Return the admin settings view. - * - * @return \Illuminate\View\View - */ - public function getSettings() - { - return view('admin.settings'); - } - - /** - * Handle settings post request. - * - * @param \Pterodactyl\Http\Requests\Admin\BaseFormRequest $request - * @return \Illuminate\Http\RedirectResponse - */ - public function postSettings(BaseFormRequest $request) - { - $this->settings->set('company', $request->input('company')); - $this->settings->set('2fa', $request->input('2fa')); - - $this->alert->success('Settings have been successfully updated.')->flash(); - - return redirect()->route('admin.settings'); - } } diff --git a/app/Http/Controllers/Admin/Settings/AdvancedController.php b/app/Http/Controllers/Admin/Settings/AdvancedController.php new file mode 100644 index 000000000..f8249acd3 --- /dev/null +++ b/app/Http/Controllers/Admin/Settings/AdvancedController.php @@ -0,0 +1,91 @@ +alert = $alert; + $this->config = $config; + $this->kernel = $kernel; + $this->settings = $settings; + } + + /** + * Render advanced Panel settings UI. + * + * @return \Illuminate\View\View + */ + public function index(): View + { + $showRecaptchaWarning = false; + if ( + $this->config->get('recaptcha._shipped_secret_key') === $this->config->get('recaptcha.secret_key') + || $this->config->get('recaptcha._shipped_website_key') === $this->config->get('recaptcha.website_key') + ) { + $showRecaptchaWarning = true; + } + + return view('admin.settings.advanced', [ + 'showRecaptchaWarning' => $showRecaptchaWarning, + ]); + } + + /** + * @param \Pterodactyl\Http\Requests\Admin\Settings\AdvancedSettingsFormRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function update(AdvancedSettingsFormRequest $request): RedirectResponse + { + foreach ($request->normalize() as $key => $value) { + $this->settings->set('settings::' . $key, $value); + } + + $this->kernel->call('queue:restart'); + $this->alert->success('Advanced settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); + + return redirect()->route('admin.settings.advanced'); + } +} diff --git a/app/Http/Controllers/Admin/Settings/IndexController.php b/app/Http/Controllers/Admin/Settings/IndexController.php new file mode 100644 index 000000000..604684da4 --- /dev/null +++ b/app/Http/Controllers/Admin/Settings/IndexController.php @@ -0,0 +1,89 @@ +alert = $alert; + $this->kernel = $kernel; + $this->settings = $settings; + $this->versionService = $versionService; + } + + /** + * Render the UI for basic Panel settings. + * + * @return \Illuminate\View\View + */ + public function index(): View + { + return view('admin.settings.index', [ + 'version' => $this->versionService, + 'languages' => $this->getAvailableLanguages(true), + ]); + } + + /** + * Handle settings update. + * + * @param \Pterodactyl\Http\Requests\Admin\Settings\BaseSettingsFormRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function update(BaseSettingsFormRequest $request): RedirectResponse + { + foreach ($request->normalize() as $key => $value) { + $this->settings->set('settings::' . $key, $value); + } + + $this->kernel->call('queue:restart'); + $this->alert->success('Panel settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); + + return redirect()->route('admin.settings'); + } +} diff --git a/app/Http/Controllers/Admin/Settings/MailController.php b/app/Http/Controllers/Admin/Settings/MailController.php new file mode 100644 index 000000000..0e5a1d737 --- /dev/null +++ b/app/Http/Controllers/Admin/Settings/MailController.php @@ -0,0 +1,112 @@ +alert = $alert; + $this->config = $config; + $this->encrypter = $encrypter; + $this->kernel = $kernel; + $this->settings = $settings; + } + + /** + * Render UI for editing mail settings. This UI should only display if + * the server is configured to send mail using SMTP. + * + * @return \Illuminate\View\View + */ + public function index(): View + { + return view('admin.settings.mail', [ + 'disabled' => $this->config->get('mail.driver') !== 'smtp', + ]); + } + + /** + * Handle request to update SMTP mail settings. + * + * @param \Pterodactyl\Http\Requests\Admin\Settings\MailSettingsFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function update(MailSettingsFormRequest $request): RedirectResponse + { + if ($this->config->get('mail.driver') !== 'smtp') { + throw new DisplayException('This feature is only available if SMTP is the selected email driver for the Panel.'); + } + + $values = $request->normalize(); + if (array_get($values, 'mail:password') === '!e') { + $values['mail:password'] = ''; + } + + foreach ($values as $key => $value) { + if (in_array($key, SettingsServiceProvider::getEncryptedKeys()) && ! empty($value)) { + $value = $this->encrypter->encrypt($value); + } + + $this->settings->set('settings::' . $key, $value); + } + + $this->kernel->call('queue:restart'); + $this->alert->success('Mail settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); + + return redirect()->route('admin.settings.mail'); + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 20594f333..ad5dead01 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Http\Controllers\Admin; @@ -160,10 +153,30 @@ class UserController extends Controller * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ public function update(UserFormRequest $request, User $user) { - $this->updateService->handle($user->id, $request->normalize()); + $this->updateService->setUserLevel(User::USER_LEVEL_ADMIN); + $data = $this->updateService->handle($user, $request->normalize()); + + if (! empty($data->get('exceptions'))) { + foreach ($data->get('exceptions') as $node => $exception) { + /** @var \GuzzleHttp\Exception\RequestException $exception */ + /** @var \GuzzleHttp\Psr7\Response|null $response */ + $response = method_exists($exception, 'getResponse') ? $exception->getResponse() : null; + $message = trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ]); + + $this->alert->danger(trans('exceptions.users.node_revocation_failed', [ + 'node' => $node, + 'error' => $message, + 'link' => route('admin.nodes.view', $node), + ]))->flash(); + } + } + $this->alert->success($this->translator->trans('admin/user.notices.account_updated'))->flash(); return redirect()->route('admin.users.view', $user->id); diff --git a/app/Http/Controllers/Base/AccountController.php b/app/Http/Controllers/Base/AccountController.php index fea7f09dd..b6a433bb4 100644 --- a/app/Http/Controllers/Base/AccountController.php +++ b/app/Http/Controllers/Base/AccountController.php @@ -1,30 +1,8 @@ - * Some Modifications (c) 2015 Dylan Seidt . - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ namespace Pterodactyl\Http\Controllers\Base; +use Pterodactyl\Models\User; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Users\UserUpdateService; @@ -48,10 +26,8 @@ class AccountController extends Controller * @param \Prologue\Alerts\AlertsMessageBag $alert * @param \Pterodactyl\Services\Users\UserUpdateService $updateService */ - public function __construct( - AlertsMessageBag $alert, - UserUpdateService $updateService - ) { + public function __construct(AlertsMessageBag $alert, UserUpdateService $updateService) + { $this->alert = $alert; $this->updateService = $updateService; } @@ -74,6 +50,7 @@ class AccountController extends Controller * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ public function update(AccountDataFormRequest $request) { @@ -86,7 +63,8 @@ class AccountController extends Controller $data = $request->only(['name_first', 'name_last', 'username']); } - $this->updateService->handle($request->user()->id, $data); + $this->updateService->setUserLevel(User::USER_LEVEL_USER); + $this->updateService->handle($request->user(), $data); $this->alert->success(trans('base.account.details_updated'))->flash(); return redirect()->route('account'); diff --git a/app/Http/Controllers/Server/Tasks/TaskManagementController.php b/app/Http/Controllers/Server/Tasks/TaskManagementController.php index e61a560fa..9e6782e93 100644 --- a/app/Http/Controllers/Server/Tasks/TaskManagementController.php +++ b/app/Http/Controllers/Server/Tasks/TaskManagementController.php @@ -149,8 +149,6 @@ class TaskManagementController extends Controller * * @param \Pterodactyl\Http\Requests\Server\ScheduleCreationFormRequest $request * @return \Illuminate\Http\RedirectResponse - * - * @throws \Illuminate\Auth\Access\AuthorizationException */ public function update(ScheduleCreationFormRequest $request): RedirectResponse { @@ -177,7 +175,7 @@ class TaskManagementController extends Controller */ public function delete(Request $request): Response { - $server = $request->attributes->get('server_data.model'); + $server = $request->attributes->get('server'); $schedule = $request->attributes->get('schedule'); $this->authorize('delete-schedule', $server); diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php index c9b51bdc2..6307669c3 100644 --- a/app/Http/Middleware/AdminAuthenticate.php +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -21,6 +21,8 @@ class AdminAuthenticate * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ public function handle(Request $request, Closure $next) { diff --git a/app/Http/Middleware/DaemonAuthenticate.php b/app/Http/Middleware/DaemonAuthenticate.php index 775a7783c..6bd2908cf 100644 --- a/app/Http/Middleware/DaemonAuthenticate.php +++ b/app/Http/Middleware/DaemonAuthenticate.php @@ -46,6 +46,8 @@ class DaemonAuthenticate * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed + * + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ public function handle(Request $request, Closure $next) { diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index bc5ff70ee..266983471 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -11,7 +11,6 @@ namespace Pterodactyl\Http\Middleware; use Closure; use Illuminate\Http\Request; -use Krucas\Settings\Settings; use Prologue\Alerts\AlertsMessageBag; class RequireTwoFactorAuthentication @@ -25,11 +24,6 @@ class RequireTwoFactorAuthentication */ private $alert; - /** - * @var \Krucas\Settings\Settings - */ - private $settings; - /** * The names of routes that should be accessable without 2FA enabled. * @@ -56,12 +50,10 @@ class RequireTwoFactorAuthentication * RequireTwoFactorAuthentication constructor. * * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Krucas\Settings\Settings $settings */ - public function __construct(AlertsMessageBag $alert, Settings $settings) + public function __construct(AlertsMessageBag $alert) { $this->alert = $alert; - $this->settings = $settings; } /** @@ -81,10 +73,7 @@ class RequireTwoFactorAuthentication return $next($request); } - switch ((int) $this->settings->get('2fa', 0)) { - case self::LEVEL_NONE: - return $next($request); - break; + switch ((int) config('pterodactyl.auth.2fa_required')) { case self::LEVEL_ADMIN: if (! $request->user()->root_admin || $request->user()->use_totp) { return $next($request); @@ -95,6 +84,9 @@ class RequireTwoFactorAuthentication return $next($request); } break; + case self::LEVEL_NONE: + default: + return $next($request); } $this->alert->danger(trans('auth.2fa_must_be_enabled'))->flash(); diff --git a/app/Http/Middleware/Server/AuthenticateAsSubuser.php b/app/Http/Middleware/Server/AuthenticateAsSubuser.php index 8ec07d94d..8f8a158ca 100644 --- a/app/Http/Middleware/Server/AuthenticateAsSubuser.php +++ b/app/Http/Middleware/Server/AuthenticateAsSubuser.php @@ -47,9 +47,8 @@ class AuthenticateAsSubuser * @param \Closure $next * @return mixed * - * @throws \Illuminate\Auth\AuthenticationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ public function handle(Request $request, Closure $next) { diff --git a/app/Http/Middleware/Server/ScheduleBelongsToServer.php b/app/Http/Middleware/Server/ScheduleBelongsToServer.php index f9f40bf3b..26da5f843 100644 --- a/app/Http/Middleware/Server/ScheduleBelongsToServer.php +++ b/app/Http/Middleware/Server/ScheduleBelongsToServer.php @@ -49,7 +49,7 @@ class ScheduleBelongsToServer $scheduleId = $this->hashids->decodeFirst($request->route()->parameter('schedule'), 0); $schedule = $this->repository->getScheduleWithTasks($scheduleId); - if (object_get($schedule, 'server_id') !== $server->id) { + if ($schedule->server_id !== $server->id) { throw new NotFoundHttpException; } diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php new file mode 100644 index 000000000..a80d8dab9 --- /dev/null +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -0,0 +1,42 @@ + 'required|in:true,false', + 'recaptcha:secret_key' => 'required|string|max:255', + 'recaptcha:website_key' => 'required|string|max:255', + 'pterodactyl:guzzle:timeout' => 'required|integer|between:1,60', + 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', + 'pterodactyl:console:count' => 'required|integer|min:1', + 'pterodactyl:console:frequency' => 'required|integer|min:10', + ]; + } + + /** + * @return array + */ + public function attributes() + { + return [ + 'recaptcha:enabled' => 'reCAPTCHA Enabled', + 'recaptcha:secret_key' => 'reCAPTCHA Secret Key', + 'recaptcha:website_key' => 'reCAPTCHA Website Key', + 'pterodactyl:guzzle:timeout' => 'HTTP Request Timeout', + 'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout', + 'pterodactyl:console:count' => 'Console Message Count', + 'pterodactyl:console:frequency' => 'Console Frequency Tick', + ]; + } +} diff --git a/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php new file mode 100644 index 000000000..0b02561dd --- /dev/null +++ b/app/Http/Requests/Admin/Settings/BaseSettingsFormRequest.php @@ -0,0 +1,36 @@ + 'required|string|max:255', + 'pterodactyl:auth:2fa_required' => 'required|integer|in:0,1,2', + 'app:locale' => ['required', 'string', Rule::in(array_keys($this->getAvailableLanguages()))], + ]; + } + + /** + * @return array + */ + public function attributes() + { + return [ + 'app:name' => 'Company Name', + 'pterodactyl:auth:2fa_required' => 'Require 2-Factor Authentication', + 'app:locale' => 'Default Language', + ]; + } +} diff --git a/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php new file mode 100644 index 000000000..269c2f6c7 --- /dev/null +++ b/app/Http/Requests/Admin/Settings/MailSettingsFormRequest.php @@ -0,0 +1,44 @@ + 'required|string', + 'mail:port' => 'required|integer|between:1,65535', + 'mail:encryption' => 'present|string|in:"",tls,ssl', + 'mail:username' => 'string|max:255', + 'mail:password' => 'string|max:255', + 'mail:from:address' => 'required|string|email', + 'mail:from:name' => 'string|max:255', + ]; + } + + /** + * Override the default normalization function for this type of request + * as we need to accept empty values on the keys. + * + * @param array $only + * @return array + */ + public function normalize($only = []) + { + $keys = array_flip(array_keys($this->rules())); + + if (empty($this->input('mail:password'))) { + unset($keys['mail:password']); + } + + return $this->only(array_flip($keys)); + } +} diff --git a/app/Http/Requests/Admin/UserFormRequest.php b/app/Http/Requests/Admin/UserFormRequest.php index e65a57f97..d705c86ed 100644 --- a/app/Http/Requests/Admin/UserFormRequest.php +++ b/app/Http/Requests/Admin/UserFormRequest.php @@ -19,7 +19,11 @@ class UserFormRequest extends AdminFormRequest public function rules() { if ($this->method() === 'PATCH') { - return User::getUpdateRulesForId($this->route()->parameter('user')->id); + $rules = User::getUpdateRulesForId($this->route()->parameter('user')->id); + + return array_merge($rules, [ + 'ignore_connection_error' => 'sometimes|nullable|boolean', + ]); } return User::getCreateRules(); @@ -30,7 +34,7 @@ class UserFormRequest extends AdminFormRequest if ($this->method === 'PATCH') { return array_merge( $this->intersect('password'), - $this->only(['email', 'username', 'name_first', 'name_last', 'root_admin']) + $this->only(['email', 'username', 'name_first', 'name_last', 'root_admin', 'ignore_connection_error']) ); } diff --git a/app/Models/Setting.php b/app/Models/Setting.php new file mode 100644 index 000000000..90d41f3d4 --- /dev/null +++ b/app/Models/Setting.php @@ -0,0 +1,39 @@ + 'required|string|between:1,255', + 'value' => 'string', + ]; +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index b9b656dc1..b3a7c33ea 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -13,7 +13,6 @@ use Pterodactyl\Observers\UserObserver; use Pterodactyl\Observers\ServerObserver; use Pterodactyl\Observers\SubuserObserver; use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider; -use DaneEveritt\LoginNotifications\NotificationServiceProvider; use Barryvdh\Debugbar\ServiceProvider as DebugbarServiceProvider; class AppServiceProvider extends ServiceProvider @@ -42,10 +41,6 @@ class AppServiceProvider extends ServiceProvider $this->app->register(DebugbarServiceProvider::class); $this->app->register(IdeHelperServiceProvider::class); } - - if (config('pterodactyl.auth.notifications')) { - $this->app->register(NotificationServiceProvider::class); - } } /** diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index bc393a883..e34520962 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -26,6 +26,7 @@ use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Repositories\Eloquent\DatabaseRepository; use Pterodactyl\Repositories\Eloquent\LocationRepository; use Pterodactyl\Repositories\Eloquent\ScheduleRepository; +use Pterodactyl\Repositories\Eloquent\SettingsRepository; use Pterodactyl\Repositories\Eloquent\DaemonKeyRepository; use Pterodactyl\Repositories\Eloquent\AllocationRepository; use Pterodactyl\Repositories\Eloquent\PermissionRepository; @@ -47,6 +48,7 @@ use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; +use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface; use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; @@ -86,10 +88,13 @@ class RepositoryServiceProvider extends ServiceProvider $this->app->bind(ServerRepositoryInterface::class, ServerRepository::class); $this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class); $this->app->bind(SessionRepositoryInterface::class, SessionRepository::class); + $this->app->bind(SettingsRepositoryInterface::class, SettingsRepository::class); $this->app->bind(SubuserRepositoryInterface::class, SubuserRepository::class); $this->app->bind(TaskRepositoryInterface::class, TaskRepository::class); $this->app->bind(UserRepositoryInterface::class, UserRepository::class); + $this->app->alias(SettingsRepositoryInterface::class, 'settings'); + // Daemon Repositories if ($this->app->make('config')->get('pterodactyl.daemon.use_new_daemon')) { $this->app->bind(ConfigurationRepositoryInterface::class, \Pterodactyl\Repositories\Wings\ConfigurationRepository::class); diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php new file mode 100644 index 000000000..dc9e9cdc6 --- /dev/null +++ b/app/Providers/SettingsServiceProvider.php @@ -0,0 +1,101 @@ +get('pterodactyl.load_environment_only', false)) { + return; + } + + // Only set the email driver settings from the database if we + // are configured using SMTP as the driver. + if ($config->get('mail.driver') === 'smtp') { + $this->keys = array_merge($this->keys, $this->emailKeys); + } + + $values = $settings->all()->mapWithKeys(function ($setting) { + return [$setting->key => $setting->value]; + })->toArray(); + + foreach ($this->keys as $key) { + $value = array_get($values, 'settings::' . $key, $config->get(str_replace(':', '.', $key))); + if (in_array($key, self::$encrypted)) { + try { + $value = $encrypter->decrypt($value); + } catch (DecryptException $exception) { + } + } + + $config->set(str_replace(':', '.', $key), $value); + } + } + + /** + * @return array + */ + public static function getEncryptedKeys(): array + { + return self::$encrypted; + } +} diff --git a/app/Repositories/Daemon/ServerRepository.php b/app/Repositories/Daemon/ServerRepository.php index 3515b26e4..f21ec197a 100644 --- a/app/Repositories/Daemon/ServerRepository.php +++ b/app/Repositories/Daemon/ServerRepository.php @@ -107,7 +107,13 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa */ public function revokeAccessKey($key) { - Assert::stringNotEmpty($key, 'First argument passed to revokeAccessKey must be a non-empty string, received %s.'); + if (is_array($key)) { + return $this->getHttpClient()->request('POST', 'keys', [ + 'json' => $key, + ]); + } + + Assert::stringNotEmpty($key, 'First argument passed to revokeAccessKey must be a non-empty string or array, received %s.'); return $this->getHttpClient()->request('DELETE', 'keys/' . $key); } diff --git a/app/Repositories/Eloquent/DaemonKeyRepository.php b/app/Repositories/Eloquent/DaemonKeyRepository.php index 533128f46..97fe0c2bb 100644 --- a/app/Repositories/Eloquent/DaemonKeyRepository.php +++ b/app/Repositories/Eloquent/DaemonKeyRepository.php @@ -24,8 +24,10 @@ namespace Pterodactyl\Repositories\Eloquent; +use Pterodactyl\Models\User; use Webmozart\Assert\Assert; use Pterodactyl\Models\DaemonKey; +use Illuminate\Support\Collection; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; @@ -83,4 +85,28 @@ class DaemonKeyRepository extends EloquentRepository implements DaemonKeyReposit return $instance; } + + /** + * Get all of the keys for a specific user including the information needed + * from their server relation for revocation on the daemon. + * + * @param \Pterodactyl\Models\User $user + * @return \Illuminate\Support\Collection + */ + public function getKeysForRevocation(User $user): Collection + { + return $this->getBuilder()->with('server:id,uuid,node_id')->where('user_id', $user->id)->get($this->getColumns()); + } + + /** + * Delete an array of daemon keys from the database. Used primarily in + * conjunction with getKeysForRevocation. + * + * @param array $ids + * @return bool|int + */ + public function deleteKeys(array $ids) + { + return $this->getBuilder()->whereIn('id', $ids)->delete(); + } } diff --git a/app/Repositories/Eloquent/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php index 04243b8a8..b893a0588 100644 --- a/app/Repositories/Eloquent/LocationRepository.php +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -18,11 +18,6 @@ class LocationRepository extends EloquentRepository implements LocationRepositor { use Searchable; - /** - * @var string - */ - protected $searchTerm; - /** * {@inheritdoc} */ diff --git a/app/Repositories/Eloquent/SettingsRepository.php b/app/Repositories/Eloquent/SettingsRepository.php new file mode 100644 index 000000000..b6937bf31 --- /dev/null +++ b/app/Repositories/Eloquent/SettingsRepository.php @@ -0,0 +1,96 @@ +clearCache($key); + $this->withoutFresh()->updateOrCreate(['key' => $key], ['value' => $value]); + } + + /** + * Retrieve a persistent setting from the database. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get(string $key, $default = null) + { + // If item has already been requested return it from the cache. If + // we already know it is missing, immediately return the default + // value. + if (array_key_exists($key, $this->cache)) { + return $this->cache[$key]; + } elseif (array_key_exists($key, $this->databaseMiss)) { + return $default; + } + + $instance = $this->getBuilder()->where('key', $key)->first(); + + if (is_null($instance)) { + $this->databaseMiss[$key] = true; + + return $default; + } + + $this->cache[$key] = $instance->value; + + return $this->cache[$key]; + } + + /** + * Remove a key from the database cache. + * + * @param string $key + * @return mixed + */ + public function forget(string $key) + { + $this->clearCache($key); + $this->deleteWhere(['key' => $key]); + } + + /** + * Remove a key from the cache. + * + * @param string $key + */ + protected function clearCache(string $key) + { + unset($this->cache[$key], $this->databaseMiss[$key]); + } +} diff --git a/app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php b/app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php new file mode 100644 index 000000000..93b8b2041 --- /dev/null +++ b/app/Services/DaemonKeys/RevokeMultipleDaemonKeysService.php @@ -0,0 +1,91 @@ +daemonRepository = $daemonRepository; + $this->repository = $repository; + } + + /** + * Grab all of the keys that exist for a single user and delete them from all + * daemon's that they are assigned to. If connection fails, this function will + * return an error. + * + * @param \Pterodactyl\Models\User $user + * @param bool $ignoreConnectionErrors + * + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException + */ + public function handle(User $user, bool $ignoreConnectionErrors = false) + { + $keys = $this->repository->getKeysForRevocation($user); + + $keys->groupBy('server.node_id')->each(function ($group, $node) use ($ignoreConnectionErrors) { + try { + $this->daemonRepository->setNode($node)->revokeAccessKey(collect($group)->pluck('secret')->toArray()); + } catch (RequestException $exception) { + if (! $ignoreConnectionErrors) { + throw new DaemonConnectionException($exception); + } + + $this->setConnectionException($node, $exception); + } + + $this->repository->deleteKeys(collect($group)->pluck('id')->toArray()); + }); + } + + /** + * Returns an array of exceptions that were returned by the handle function. + * + * @return RequestException[] + */ + public function getExceptions() + { + return $this->exceptions; + } + + /** + * Add an exception for a node to the array. + * + * @param int $node + * @param \GuzzleHttp\Exception\RequestException $exception + */ + protected function setConnectionException(int $node, RequestException $exception) + { + $this->exceptions[$node] = $exception; + } +} diff --git a/app/Services/Users/UserUpdateService.php b/app/Services/Users/UserUpdateService.php index dbebe4d95..9e755dd9f 100644 --- a/app/Services/Users/UserUpdateService.php +++ b/app/Services/Users/UserUpdateService.php @@ -1,59 +1,79 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Users; +use Pterodactyl\Models\User; +use Illuminate\Support\Collection; use Illuminate\Contracts\Hashing\Hasher; +use Pterodactyl\Traits\Services\HasUserLevels; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; +use Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService; class UserUpdateService { + use HasUserLevels; + /** * @var \Illuminate\Contracts\Hashing\Hasher */ - protected $hasher; + private $hasher; /** * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface */ - protected $repository; + private $repository; + + /** + * @var \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService + */ + private $revocationService; /** * UpdateService constructor. * - * @param \Illuminate\Contracts\Hashing\Hasher $hasher - * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository + * @param \Illuminate\Contracts\Hashing\Hasher $hasher + * @param \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService $revocationService + * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( Hasher $hasher, + RevokeMultipleDaemonKeysService $revocationService, UserRepositoryInterface $repository ) { $this->hasher = $hasher; $this->repository = $repository; + $this->revocationService = $revocationService; } /** - * Update the user model instance. + * Update the user model instance. If the user has been removed as an administrator + * revoke all of the authentication tokens that have beenn assigned to their account. * - * @param int $id - * @param array $data - * @return mixed + * @param \Pterodactyl\Models\User $user + * @param array $data + * @return \Illuminate\Support\Collection * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function handle($id, array $data) + public function handle(User $user, array $data): Collection { - if (isset($data['password'])) { + if (array_has($data, 'password')) { $data['password'] = $this->hasher->make($data['password']); } - return $this->repository->update($id, $data); + if ($this->isUserLevel(User::USER_LEVEL_ADMIN)) { + if (array_get($data, 'root_admin', 0) == 0 && $user->root_admin) { + $this->revocationService->handle($user, array_get($data, 'ignore_connection_error', false)); + } + } else { + unset($data['root_admin']); + } + + return collect([ + 'model' => $this->repository->update($user->id, $data), + 'exceptions' => $this->revocationService->getExceptions(), + ]); } } diff --git a/app/Traits/Helpers/AvailableLanguages.php b/app/Traits/Helpers/AvailableLanguages.php new file mode 100644 index 000000000..f44771f6d --- /dev/null +++ b/app/Traits/Helpers/AvailableLanguages.php @@ -0,0 +1,56 @@ +getFilesystemInstance()->directories(resource_path('lang')))->mapWithKeys(function ($path) use ($localize) { + $code = basename($path); + $value = $localize ? $this->getIsoInstance()->nativeByCode1($code) : $this->getIsoInstance()->languageByCode1($code); + + return [$code => title_case($value)]; + })->toArray(); + } + + /** + * Return an instance of the filesystem for getting a folder listing. + * + * @return \Illuminate\Filesystem\Filesystem + */ + private function getFilesystemInstance(): Filesystem + { + return $this->filesystem = $this->filesystem ?: app()->make(Filesystem::class); + } + + /** + * Return an instance of the ISO639 class for generating names. + * + * @return \Matriphe\ISO639\ISO639 + */ + private function getIsoInstance(): ISO639 + { + return $this->iso639 = $this->iso639 ?: app()->make(ISO639::class); + } +} diff --git a/composer.json b/composer.json index fabe01f0f..6b1271f84 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "laravel/framework": "5.4.27", "laravel/tinker": "1.0.1", "lord/laroute": "~2.4.5", + "matriphe/iso-639": "^1.2", "mtdowling/cron-expression": "^1.2", "nesbot/carbon": "^1.22", "nicolaslopezj/searchable": "^1.9", diff --git a/composer.lock b/composer.lock index 9895b8330..139e3c1c3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "a393763d136e25a93fd5b636229496cf", + "content-hash": "bd42f43877e96cca4d4af755c590eb25", "packages": [ { "name": "appstract/laravel-blade-directives", @@ -687,57 +687,6 @@ ], "time": "2014-09-09T13:34:57+00:00" }, - { - "name": "edvinaskrucas/settings", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/edvinaskrucas/settings.git", - "reference": "23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/edvinaskrucas/settings/zipball/23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022", - "reference": "23f2a912ca8f5b6ba550721a6fc0e6d1acaa9022", - "shasum": "" - }, - "require": { - "illuminate/console": "^5.2", - "illuminate/database": "^5.2", - "illuminate/filesystem": "^5.2", - "illuminate/support": "^5.2", - "php": "^5.5|^7.0" - }, - "require-dev": { - "mockery/mockery": "0.9.*" - }, - "type": "library", - "autoload": { - "psr-0": { - "Krucas\\Settings\\": "src/" - }, - "files": [ - "src/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Edvinas Kručas", - "email": "edv.krucas@gmail.com" - } - ], - "description": "Persistent settings package for Laravel framework.", - "keywords": [ - "Settings", - "laravel", - "persistent settings" - ], - "time": "2016-01-19T13:50:39+00:00" - }, { "name": "erusev/parsedown", "version": "1.6.3", @@ -1674,6 +1623,50 @@ ], "time": "2017-09-04T02:25:29+00:00" }, + { + "name": "matriphe/iso-639", + "version": "1.2", + "source": { + "type": "git", + "url": "https://github.com/matriphe/php-iso-639.git", + "reference": "0245d844daeefdd22a54b47103ffdb0e03c323e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matriphe/php-iso-639/zipball/0245d844daeefdd22a54b47103ffdb0e03c323e1", + "reference": "0245d844daeefdd22a54b47103ffdb0e03c323e1", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^4.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matriphe\\ISO639\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Muhammad Zamroni", + "email": "halo@matriphe.com" + } + ], + "description": "PHP library to convert ISO-639-1 code to language name.", + "keywords": [ + "639", + "iso", + "iso-639", + "lang", + "language", + "laravel" + ], + "time": "2017-07-19T15:11:19+00:00" + }, { "name": "monolog/monolog", "version": "1.23.0", diff --git a/config/app.php b/config/app.php index 2f9da6704..d56b80634 100644 --- a/config/app.php +++ b/config/app.php @@ -1,5 +1,6 @@ env('APP_ENV', 'production'), @@ -14,7 +15,7 @@ return [ | framework needs to place the application's name in a notification or | any other location as required by the application or its packages. */ - 'name' => 'Pterodactyl', + 'name' => env('APP_NAME', 'Pterodactyl'), /* |-------------------------------------------------------------------------- @@ -158,6 +159,7 @@ return [ /* * Application Service Providers... */ + Pterodactyl\Providers\SettingsServiceProvider::class, Pterodactyl\Providers\AppServiceProvider::class, Pterodactyl\Providers\AuthServiceProvider::class, Pterodactyl\Providers\EventServiceProvider::class, @@ -173,7 +175,6 @@ return [ */ igaster\laravelTheme\themeServiceProvider::class, Prologue\Alerts\AlertsServiceProvider::class, - Krucas\Settings\Providers\SettingsServiceProvider::class, Fideloper\Proxy\TrustedProxyServiceProvider::class, Laracasts\Utilities\JavaScript\JavaScriptServiceProvider::class, Lord\Laroute\LarouteServiceProvider::class, @@ -228,7 +229,6 @@ return [ 'Response' => Illuminate\Support\Facades\Response::class, 'Route' => Illuminate\Support\Facades\Route::class, 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Settings' => Krucas\Settings\Facades\Settings::class, 'Session' => Illuminate\Support\Facades\Session::class, 'Storage' => Illuminate\Support\Facades\Storage::class, 'Theme' => igaster\laravelTheme\Facades\Theme::class, diff --git a/config/pterodactyl.php b/config/pterodactyl.php index ad371bce9..523080ae3 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -1,6 +1,18 @@ (bool) env('APP_ENVIRONMENT_ONLY', false), + /* |-------------------------------------------------------------------------- | Service Author @@ -22,7 +34,7 @@ return [ | Should login success and failure events trigger an email to the user? */ 'auth' => [ - 'notifications' => env('LOGIN_NOTIFICATIONS', false), + '2fa_required' => env('APP_2FA_REQUIRED', 0), '2fa' => [ 'bytes' => 32, 'window' => env('APP_2FA_WINDOW', 4), diff --git a/config/recaptcha.php b/config/recaptcha.php index 1bac5a877..22d739481 100644 --- a/config/recaptcha.php +++ b/config/recaptcha.php @@ -14,12 +14,14 @@ return [ /* * Use a custom secret key, we use our public one by default */ - 'secret_key' => env('RECAPTCHA_SECRET_KEY', '6LekAxoUAAAAAPW-PxNWaCLH76WkClMLSa2jImwD'), + 'secret_key' => env('RECAPTCHA_SECRET_KEY', '6LcJcjwUAAAAALOcDJqAEYKTDhwELCkzUkNDQ0J5'), + '_shipped_secret_key' => '6LcJcjwUAAAAALOcDJqAEYKTDhwELCkzUkNDQ0J5', /* * Use a custom website key, we use our public one by default */ - 'website_key' => env('RECAPTCHA_WEBSITE_KEY', '6LekAxoUAAAAADjWZJ4ufcDRZBBiH9vfHawqRbup'), + 'website_key' => env('RECAPTCHA_WEBSITE_KEY', '6LcJcjwUAAAAAO_Xqjrtj9wWufUpYRnK6BW8lnfn'), + '_shipped_website_key' => '6LcJcjwUAAAAAO_Xqjrtj9wWufUpYRnK6BW8lnfn', /* * Domain verification is enabled by default and compares the domain used when solving the captcha diff --git a/config/settings.php b/config/settings.php deleted file mode 100644 index c6a75fb01..000000000 --- a/config/settings.php +++ /dev/null @@ -1,113 +0,0 @@ - env('SETTINGS_DRIVER', 'database'), - - /* - |-------------------------------------------------------------------------- - | Enable / Disable caching - |-------------------------------------------------------------------------- - | - | If it is enabled all values gets cached after accessing it. - | - */ - 'cache' => true, - - /* - |-------------------------------------------------------------------------- - | Enable / Disable value encryption - |-------------------------------------------------------------------------- - | - | If it is enabled all values gets encrypted and decrypted. - | - */ - 'encryption' => env('SETTINGS_ENCRYPTION', false), - - /* - |-------------------------------------------------------------------------- - | Enable / Disable events - |-------------------------------------------------------------------------- - | - | If it is enabled various settings related events will be fired. - | - */ - 'events' => true, - - /* - |-------------------------------------------------------------------------- - | Repositories Configuration - |-------------------------------------------------------------------------- - | - | Here you may configure the driver information for each repository that - | is used by your application. A default configuration has been added - | for each back-end shipped with this package. You are free to add more. - | - */ - - 'repositories' => [ - 'database' => [ - 'driver' => 'database', - 'connection' => env('DB_CONNECTION', 'mysql'), - 'table' => 'settings', - ], - ], - - /* - |-------------------------------------------------------------------------- - | Key generator class - |-------------------------------------------------------------------------- - | - | Key generator is used to generate keys based on setting key and context. - | - */ - 'key_generator' => \Krucas\Settings\KeyGenerators\KeyGenerator::class, - - /* - |-------------------------------------------------------------------------- - | Context serializer class - |-------------------------------------------------------------------------- - | - | Context serializer serializes context. - | It is used with "Krucas\Settings\KeyGenerators\KeyGenerator" class. - | - */ - 'context_serializer' => \Krucas\Settings\ContextSerializers\ContextSerializer::class, - - /* - |-------------------------------------------------------------------------- - | Value serializer class - |-------------------------------------------------------------------------- - | - | Value serializer serializes / unserializes given value. - | - */ - 'value_serializer' => \Krucas\Settings\ValueSerializers\ValueSerializer::class, - - /* - |-------------------------------------------------------------------------- - | Override application config values - |-------------------------------------------------------------------------- - | - | If defined, settings package will override these config values from persistent - | settings repository. - | - | Sample: - | "app.fallback_locale", - | "app.locale" => "settings.locale", - | - */ - - 'override' => [ - ], -]; diff --git a/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php b/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php new file mode 100644 index 000000000..d28109598 --- /dev/null +++ b/database/migrations/2017_12_04_184012_DropAllocationsWhenNodeIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['node_id']); + + $table->foreign('node_id')->references('id')->on('nodes')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropForeign(['node_id']); + + $table->foreign('node_id')->references('id')->on('nodes'); + }); + } +} diff --git a/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php new file mode 100644 index 000000000..1bdaf6477 --- /dev/null +++ b/database/migrations/2017_12_12_220426_MigrateSettingsTableToNewFormat.php @@ -0,0 +1,30 @@ +truncate(); + Schema::table('settings', function (Blueprint $table) { + $table->increments('id')->first(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('id'); + }); + } +} diff --git a/database/seeds/eggs/source-engine/egg-garrys-mod.json b/database/seeds/eggs/source-engine/egg-garrys-mod.json index a6334b76f..3b64be3ff 100644 --- a/database/seeds/eggs/source-engine/egg-garrys-mod.json +++ b/database/seeds/eggs/source-engine/egg-garrys-mod.json @@ -17,7 +17,7 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/bash\n# Garry's Mod Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 4020 +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", + "script": "#!\/bin\/bash\n# Garry's Mod Installation Script\n#\n# Server Files: \/mnt\/server\napt -y update\napt -y --no-install-recommends install curl lib32gcc1 lib32stdc++6 ca-certificates\n\ncd \/tmp\ncurl -sSL -o steamcmd.tar.gz http:\/\/media.steampowered.com\/installer\/steamcmd_linux.tar.gz\n\nmkdir -p \/mnt\/server\/steamcmd\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\ncd \/mnt\/server\/steamcmd\n\n# SteamCMD fails otherwise for some reason, even running as root.\n# This is changed at the end of the install process anyways.\nchown -R root:root \/mnt\n\nexport HOME=\/mnt\/server\n.\/steamcmd.sh +login anonymous +force_install_dir \/mnt\/server +app_update 4020 +quit\n\nmkdir -p \/mnt\/server\/.steam\/sdk32\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so", "container": "ubuntu:16.04", "entrypoint": "bash" } @@ -42,4 +42,4 @@ "rules": "required|string|alpha_num|size:32" } ] -} \ No newline at end of file +} diff --git a/phpunit.xml b/phpunit.xml index ceb832d08..89b3c0b22 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -32,5 +32,6 @@ + diff --git a/public/themes/pterodactyl/js/frontend/server.socket.js b/public/themes/pterodactyl/js/frontend/server.socket.js index ef6986168..f43409fd4 100644 --- a/public/themes/pterodactyl/js/frontend/server.socket.js +++ b/public/themes/pterodactyl/js/frontend/server.socket.js @@ -66,6 +66,7 @@ var Server = (function () { delay: 0, }); } + setStatusIcon(999); }); Socket.io.on('connect_error', function (err) { @@ -77,6 +78,7 @@ var Server = (function () { delay: 0, }); } + setStatusIcon(999); }); // Connected to Socket Successfully @@ -111,6 +113,7 @@ var Server = (function () { $('#server_status_icon').html(' Stopping'); break; default: + $('#server_status_icon').html(' Connection Error'); break; } } diff --git a/resources/lang/en/exceptions.php b/resources/lang/en/exceptions.php index e5bee177d..f32b9c71a 100644 --- a/resources/lang/en/exceptions.php +++ b/resources/lang/en/exceptions.php @@ -59,4 +59,7 @@ return [ 'locations' => [ 'has_nodes' => 'Cannot delete a location that has active nodes attached to it.', ], + 'users' => [ + 'node_revocation_failed' => 'Failed to revoke keys on Node #:node. :error', + ], ]; diff --git a/resources/themes/pterodactyl/admin/settings.blade.php b/resources/themes/pterodactyl/admin/settings.blade.php deleted file mode 100644 index 3e0407e67..000000000 --- a/resources/themes/pterodactyl/admin/settings.blade.php +++ /dev/null @@ -1,69 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.admin') - -@section('title') - Settings -@endsection - -@section('content-header') -

Panel SettingsConfigure Pterodactyl to your liking.

- -@endsection - -@section('content') -
-
-
-
-

Panel Settings

-
-
-
-
-
- -
- -

This is the name that is used throughout the panel and in emails sent to clients.

-
-
-
- -
-
- - - -
-

For improved security you can require all administrators to have 2-Factor authentication enabled, or even require it for all users on the Panel.

-
-
-
-
-
-
In order to modify your SMTP settings for sending mail you will need to run php artisan p:environment:mail in this project's root folder.
-
-
-
- -
-
-
-
-@endsection diff --git a/resources/themes/pterodactyl/admin/settings/advanced.blade.php b/resources/themes/pterodactyl/admin/settings/advanced.blade.php new file mode 100644 index 000000000..5a7b4d724 --- /dev/null +++ b/resources/themes/pterodactyl/admin/settings/advanced.blade.php @@ -0,0 +1,117 @@ +@extends('layouts.admin') +@include('partials/admin.settings.nav', ['activeTab' => 'advanced']) + +@section('title') + Advanced Settings +@endsection + +@section('content-header') +

Advanced SettingsConfigure advanced settings for Pterodactyl.

+ +@endsection + +@section('content') + @yield('settings::nav') +
+
+
+
+
+

reCAPTCHA

+
+
+
+
+ +
+ +

If enabled, login forms and password reset forms will do a silent captcha check and display a visible captcha if needed.

+
+
+
+ +
+ +

Used for communication between your site and Google. Be sure to keep it a secret.

+
+
+
+ +
+ +
+
+
+ @if($showRecaptchaWarning) +
+
+
+ You are currently using reCAPTCHA keys that were shipped with this Panel. For improved security it is recommended to generate new invisible reCAPTCHA keys that tied specifically to your website. +
+
+
+ @endif +
+
+
+
+

HTTP Connections

+
+
+
+
+ +
+ +

The amount of time in seconds to wait for a connection to be opened before throwing an error.

+
+
+
+ +
+ +

The amount of time in seconds to wait for a request to be completed before throwing an error.

+
+
+
+
+
+
+
+

Console

+
+
+
+
+ +
+ +

The number of messages to be pushed to the console per frequency tick.

+
+
+
+ +
+ +

The amount of time in milliseconds between each console message sending tick.

+
+
+
+
+
+
+ +
+
+
+
+@endsection diff --git a/resources/themes/pterodactyl/admin/settings/index.blade.php b/resources/themes/pterodactyl/admin/settings/index.blade.php new file mode 100644 index 000000000..62ef09631 --- /dev/null +++ b/resources/themes/pterodactyl/admin/settings/index.blade.php @@ -0,0 +1,75 @@ +@extends('layouts.admin') +@include('partials/admin.settings.nav', ['activeTab' => 'basic']) + +@section('title') + Settings +@endsection + +@section('content-header') +

Panel SettingsConfigure Pterodactyl to your liking.

+ +@endsection + +@section('content') + @yield('settings::nav') +
+
+
+
+

Panel Settings

+
+
+
+
+
+ +
+ +

This is the name that is used throughout the panel and in emails sent to clients.

+
+
+
+ +
+
+ @php + $level = old('pterodactyl:auth:2fa_required', config('pterodactyl.auth.2fa_required')); + @endphp + + + +
+

If enabled, any account falling into the selected grouping will be required to have 2-Factor authentication enabled to use the Panel.

+
+
+
+ +
+ +

The default language to use when rendering UI components.

+
+
+
+
+ +
+
+
+
+@endsection diff --git a/resources/themes/pterodactyl/admin/settings/mail.blade.php b/resources/themes/pterodactyl/admin/settings/mail.blade.php new file mode 100644 index 000000000..40403993f --- /dev/null +++ b/resources/themes/pterodactyl/admin/settings/mail.blade.php @@ -0,0 +1,108 @@ +@extends('layouts.admin') +@include('partials/admin.settings.nav', ['activeTab' => 'mail']) + +@section('title') + Mail Settings +@endsection + +@section('content-header') +

Mail SettingsConfigure how Pterodactyl should handle sending emails.

+ +@endsection + +@section('content') + @yield('settings::nav') +
+
+
+
+

Email Settings

+
+ @if($disabled) +
+
+
+
+ This interface is limited to instances using SMTP as the mail driver. Please either use php artisan p:environment:mail command to update your email settings, or set MAIL_DRIVER=smtp in your environment file. +
+
+
+
+ @else +
+
+
+
+ +
+ +

Enter the SMTP server address that mail should be sent through.

+
+
+
+ +
+ +

Enter the SMTP server port that mail should be sent through.

+
+
+
+ +
+ @php + $encryption = old('mail:encryption', config('mail.encryption')); + @endphp + +

Select the type of encryption to use when sending mail.

+
+
+
+ +
+ +

The username to use when connecting to the SMTP server.

+
+
+
+ +
+ +

The password to use in conjunction with the SMTP username. Leave blank to continue using the existing password. To set the password to an empty value enter !e into the field.

+
+
+
+
+
+
+ +
+ +

Enter an email address that all outgoing emails will originate from.

+
+
+
+ +
+ +

The name that emails should appear to come from.

+
+
+
+
+ +
+ @endif +
+
+
+@endsection diff --git a/resources/themes/pterodactyl/admin/users/index.blade.php b/resources/themes/pterodactyl/admin/users/index.blade.php index 0c4289f32..dd95f222b 100644 --- a/resources/themes/pterodactyl/admin/users/index.blade.php +++ b/resources/themes/pterodactyl/admin/users/index.blade.php @@ -53,7 +53,7 @@ @foreach ($users as $user) {{ $user->id }} - {{ $user->email }} + {{ $user->email }} @if($user->root_admin)@endif {{ $user->name_last }}, {{ $user->name_first }} {{ $user->username }} diff --git a/resources/themes/pterodactyl/admin/users/view.blade.php b/resources/themes/pterodactyl/admin/users/view.blade.php index 1c1946ecb..61604a0aa 100644 --- a/resources/themes/pterodactyl/admin/users/view.blade.php +++ b/resources/themes/pterodactyl/admin/users/view.blade.php @@ -66,10 +66,11 @@
-
- +
+
+

Leave blank to keep this user's password the same. User will not receive any notification if password is changed.

@@ -90,6 +91,11 @@

Setting this to 'Yes' gives a user full administrative access.

+
+ + +

If checked, any errors thrown while revoking keys across nodes will be ignored. You should avoid this checkbox if possible as any non-revoked keys could continue to be active for up to 24 hours after this account is changed. If you are needing to revoke account permissions immediately and are facing node issues, you should check this box and then restart any nodes that failed to be updated to clear out any stored keys.

+
diff --git a/resources/themes/pterodactyl/auth/passwords/reset.blade.php b/resources/themes/pterodactyl/auth/passwords/reset.blade.php index 51632fbf3..08968cd62 100644 --- a/resources/themes/pterodactyl/auth/passwords/reset.blade.php +++ b/resources/themes/pterodactyl/auth/passwords/reset.blade.php @@ -68,6 +68,7 @@
{!! csrf_field() !!} +
diff --git a/resources/themes/pterodactyl/layouts/admin.blade.php b/resources/themes/pterodactyl/layouts/admin.blade.php index 0c2108ffa..262e59cd5 100644 --- a/resources/themes/pterodactyl/layouts/admin.blade.php +++ b/resources/themes/pterodactyl/layouts/admin.blade.php @@ -8,7 +8,7 @@ - {{ Settings::get('company', 'Pterodactyl') }} - @yield('title') + {{ config('app.name', 'Pterodactyl') }} - @yield('title') @@ -44,7 +44,7 @@