Merge branch 'develop' into feature/api-key-changes

This commit is contained in:
Dane Everitt 2017-11-26 13:20:25 -06:00
commit 80ddd5b659
21 changed files with 124 additions and 136 deletions

View file

@ -2,10 +2,10 @@ APP_ENV=production
APP_DEBUG=false
APP_KEY=SomeRandomString3232RandomString
APP_THEME=pterodactyl
APP_TIMEZONE=UTC
APP_TIMEZONE=America/New_York
APP_CLEAR_TASKLOG=720
APP_DELETE_MINUTES=10
APP_URL=http://yoursite.com/
APP_URL=
DB_HOST=127.0.0.1
DB_PORT=3306
@ -13,8 +13,8 @@ DB_DATABASE=panel
DB_USERNAME=pterodactyl
DB_PASSWORD=
CACHE_DRIVER=redis
SESSION_DRIVER=database
CACHE_DRIVER=
SESSION_DRIVER=
HASHIDS_SALT=
HASHIDS_LENGTH=8
@ -25,9 +25,9 @@ MAIL_PORT=2525
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
MAIL_FROM=you@example.com
MAIL_FROM=no-reply@example.com
QUEUE_DRIVER=database
QUEUE_DRIVER=
QUEUE_HIGH=high
QUEUE_STANDARD=standard
QUEUE_LOW=low

View file

@ -13,9 +13,10 @@ services:
before_install:
- mysql -e 'CREATE DATABASE IF NOT EXISTS travis;'
before_script:
- echo 'opcache.enable_cli=1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- cp .env.travis .env
- composer install --no-interaction --prefer-dist --no-suggest --verbose
- php artisan migrate --seed -v
- composer install --no-interaction --prefer-dist --no-suggest
- php artisan migrate --seed
script:
- vendor/bin/phpunit --coverage-clover coverage.xml
notifications:

View file

@ -4,6 +4,12 @@ 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.0-beta.3 (Derelict Dermodactylus)
### Fixed
* `[beta.2]` — Fixes a bug that would cause an endless exception message stream in the console when attemping to setup environment settings in certain instances.
* `[beta.2]` — Fixes a bug causing the dropdown menu for a server's egg to display the wrong selected value.
* `[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.
### 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.

View file

@ -9,6 +9,7 @@
namespace Pterodactyl\Console\Commands\Environment;
use DateTimeZone;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel;
use Pterodactyl\Traits\Commands\EnvironmentWriterTrait;
@ -18,6 +19,25 @@ class AppSettingsCommand extends Command
{
use EnvironmentWriterTrait;
const ALLOWED_CACHE_DRIVERS = [
'redis' => 'Redis (recommended)',
'memcached' => 'Memcached',
];
const ALLOWED_SESSION_DRIVERS = [
'redis' => 'Redis (recommended)',
'memcached' => 'Memcached',
'database' => 'MySQL Database',
'file' => 'Filesystem',
'cookie' => 'Cookie',
];
const ALLOWED_QUEUE_DRIVERS = [
'redis' => 'Redis (recommended)',
'database' => 'MySQL Database',
'sync' => 'Sync',
];
/**
* @var \Illuminate\Contracts\Console\Kernel
*/
@ -37,11 +57,13 @@ class AppSettingsCommand extends Command
* @var string
*/
protected $signature = 'p:environment:setup
{--new-salt : Wether or not to generate a new salt for Hashids.}
{--author= : The email that services created on this instance should be linked to.}
{--url= : The URL that this Panel is running on.}
{--timezone= : The timezone to use for Panel times.}
{--cache= : The cache driver backend to use.}
{--session= : The session driver backend to use.}
{--queue= : The queue driver backend to use.}
{--redis-host= : Redis host to use for connections.}
{--redis-pass= : Password used to connect to redis.}
{--redis-port= : Port to connect to redis over.}';
@ -72,7 +94,7 @@ class AppSettingsCommand extends Command
*/
public function handle()
{
if (empty($this->config->get('hashids.salt')) || $this->option('--new-salt')) {
if (empty($this->config->get('hashids.salt')) || $this->option('new-salt')) {
$this->variables['HASHIDS_SALT'] = str_random(20);
}
@ -87,33 +109,31 @@ class AppSettingsCommand extends Command
);
$this->output->comment(trans('command/messages.environment.app.timezone_help'));
$this->variables['APP_TIMEZONE'] = $this->option('timezone') ?? $this->ask(
trans('command/messages.environment.app.timezone'), $this->config->get('app.timezone')
$this->variables['APP_TIMEZONE'] = $this->option('timezone') ?? $this->anticipate(
trans('command/messages.environment.app.timezone'),
DateTimeZone::listIdentifiers(DateTimeZone::ALL),
$this->config->get('app.timezone')
);
$selected = $this->config->get('cache.default', 'redis');
$this->variables['CACHE_DRIVER'] = $this->option('cache') ?? $this->choice(
trans('command/messages.environment.app.cache_driver'), [
'redis' => 'Redis (recommended)',
'memcached' => 'Memcached',
], $this->config->get('cache.default', 'redis')
trans('command/messages.environment.app.cache_driver'),
self::ALLOWED_CACHE_DRIVERS,
array_key_exists($selected, self::ALLOWED_CACHE_DRIVERS) ? $selected : null
);
$selected = $this->config->get('session.driver', 'redis');
$this->variables['SESSION_DRIVER'] = $this->option('session') ?? $this->choice(
trans('command/messages.environment.app.session_driver'), [
'redis' => 'Redis (recommended)',
'memcached' => 'Memcached',
'database' => 'MySQL Database',
'file' => 'Filesystem',
'cookie' => 'Cookie',
], $this->config->get('session.driver', 'redis')
trans('command/messages.environment.app.session_driver'),
self::ALLOWED_SESSION_DRIVERS,
array_key_exists($selected, self::ALLOWED_SESSION_DRIVERS) ? $selected : null
);
$this->variables['QUEUE_DRIVER'] = $this->option('session') ?? $this->choice(
trans('command/messages.environment.app.session_driver'), [
'redis' => 'Redis (recommended)',
'database' => 'MySQL Database',
'sync' => 'Sync',
], $this->config->get('queue.driver', 'redis')
$selected = $this->config->get('queue.default', 'redis');
$this->variables['QUEUE_DRIVER'] = $this->option('queue') ?? $this->choice(
trans('command/messages.environment.app.queue_driver'),
self::ALLOWED_QUEUE_DRIVERS,
array_key_exists($selected, self::ALLOWED_QUEUE_DRIVERS) ? $selected : null
);
$this->checkForRedis();

View file

@ -9,6 +9,6 @@
namespace Pterodactyl\Exceptions;
class DisplayValidationException extends PterodactylException
class DisplayValidationException extends DisplayException
{
}

View file

@ -319,14 +319,14 @@ class ServersController extends Controller
/**
* Display startup configuration page for a server.
*
* @param int $server
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function viewStartup($server)
public function viewStartup(Server $server)
{
$parameters = $this->repository->getVariablesWithValues($server, true);
$parameters = $this->repository->getVariablesWithValues($server->id, true);
if (! $parameters->server->installed) {
abort(404);
}
@ -334,6 +334,7 @@ class ServersController extends Controller
$nests = $this->nestRepository->getWithEggs();
Javascript::put([
'server' => $server,
'nests' => $nests->map(function ($item) {
return array_merge($item->toArray(), [
'eggs' => $item->eggs->keyBy('id')->toArray(),

View file

@ -38,6 +38,9 @@ class ConsoleController extends Controller
$server = $request->attributes->get('server');
$this->setRequest($request)->injectJavascript([
'server' => [
'cpu' => $server->cpu,
],
'meta' => [
'saveFile' => route('server.files.save', $server->uuidShort),
'csrfToken' => csrf_token(),

View file

@ -18,6 +18,10 @@ class DatabaseHostFormRequest extends AdminFormRequest
*/
public function rules()
{
if (! $this->has('node_id')) {
$this->merge(['node_id' => null]);
}
if ($this->method() !== 'POST') {
return DatabaseHost::getUpdateRulesForId($this->route()->parameter('host')->id);
}

View file

@ -63,14 +63,13 @@ class DatabaseHost extends Model implements CleansAttributes, ValidableContract
'host' => 'required',
'port' => 'required',
'username' => 'required',
'node_id' => 'sometimes|required',
'node_id' => 'sometimes',
];
/**
* Validation rules to assign to this model.
*
* @var array
* @todo the node_id field doesn't validate correctly if no node is provided in request
*/
protected static $dataIntegrityRules = [
'name' => 'string|max:255',
@ -78,7 +77,7 @@ class DatabaseHost extends Model implements CleansAttributes, ValidableContract
'port' => 'numeric|between:1,65535',
'username' => 'string|max:32',
'password' => 'nullable|string',
'node_id' => 'nullable|exists:nodes,id',
'node_id' => 'nullable|integer|exists:nodes,id',
];
/**

View file

@ -78,9 +78,10 @@ class StartupModificationService
* @param \Pterodactyl\Models\Server $server
* @param array $data
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function handle(Server $server, array $data)
{

View file

@ -11,8 +11,8 @@ namespace Pterodactyl\Services\Servers;
use Pterodactyl\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Validation\ValidationException;
use Pterodactyl\Traits\Services\HasUserLevels;
use Pterodactyl\Exceptions\DisplayValidationException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
@ -25,22 +25,22 @@ class VariableValidatorService
/**
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface
*/
protected $optionVariableRepository;
private $optionVariableRepository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $serverRepository;
private $serverRepository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface
*/
protected $serverVariableRepository;
private $serverVariableRepository;
/**
* @var \Illuminate\Contracts\Validation\Factory
*/
protected $validator;
private $validator;
/**
* VariableValidatorService constructor.
@ -68,32 +68,32 @@ class VariableValidatorService
* @param int $egg
* @param array $fields
* @return \Illuminate\Support\Collection
* @throws \Illuminate\Validation\ValidationException
*/
public function handle(int $egg, array $fields = []): Collection
{
$variables = $this->optionVariableRepository->findWhere([['egg_id', '=', $egg]]);
$messages = $this->validator->make([], []);
return $variables->map(function ($item) use ($fields) {
$response = $variables->map(function ($item) use ($fields, $messages) {
// Skip doing anything if user is not an admin and
// variable is not user viewable or editable.
if (! $this->isUserLevel(User::USER_LEVEL_ADMIN) && (! $item->user_editable || ! $item->user_viewable)) {
return false;
}
$validator = $this->validator->make([
$v = $this->validator->make([
'variable_value' => array_get($fields, $item->env_variable),
], [
'variable_value' => $item->rules,
], [], [
'variable_value' => trans('validation.internal.variable_value', ['env' => $item->name]),
]);
if ($validator->fails()) {
throw new DisplayValidationException(json_encode(
collect([
'notice' => [
trans('admin/server.exceptions.bad_variable', ['name' => $item->name]),
],
])->merge($validator->errors()->toArray())
));
if ($v->fails()) {
foreach ($v->getMessageBag()->all() as $message) {
$messages->getMessageBag()->add($item->env_variable, $message);
}
}
return (object) [
@ -104,5 +104,11 @@ class VariableValidatorService
})->filter(function ($item) {
return is_object($item);
});
if (! empty($messages->getMessageBag()->all())) {
throw new ValidationException($messages);
}
return $response;
}
}

View file

@ -45,7 +45,7 @@ trait JavascriptInjection
$server = $request->attributes->get('server');
$token = $request->attributes->get('server_token');
$response = array_merge([
$response = array_merge_recursive([
'server' => [
'uuid' => $server->uuid,
'uuidShort' => $server->uuidShort,

View file

@ -36,7 +36,7 @@
"name": "OxideMod",
"description": "Set whether you want the server to use and auto update OxideMod or not. Valid options are \"1\" for true and \"0\" for false.",
"env_variable": "OXIDE",
"default_value": "false",
"default_value": "0",
"user_viewable": 1,
"user_editable": 1,
"rules": "required|boolean"

View file

@ -16,7 +16,7 @@ return [
'default_allocation_not_found' => 'The requested default allocation was not found in this server\'s allocations.',
],
'alerts' => [
'startup_changed' => 'The startup configuration for this server has been updated. If this server\'s service or option was changed a reinstall will be occuring now.',
'startup_changed' => 'The startup configuration for this server has been updated. If this server\'s nest or egg was changed a reinstall will be occuring now.',
'server_deleted' => 'Server has successfully been deleted from the system.',
'server_created' => 'Server was successfully created on the panel. Please allow the daemon a few minutes to completely install this server.',
'build_updated' => 'The build details for this server have been updated. Some changes may require a restart to take effect.',

View file

@ -82,6 +82,7 @@ return [
'timezone' => 'Application Timezone',
'cache_driver' => 'Cache Driver',
'session_driver' => 'Session Driver',
'queue_driver' => 'Queue Driver',
'using_redis' => 'You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.',
'redis_host' => 'Redis Host',
'redis_password' => 'Redis Password',

View file

@ -85,23 +85,6 @@ return [
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
@ -114,4 +97,9 @@ return [
*/
'attributes' => [],
// Internal validation logic for Pterodactyl
'internal' => [
'variable_value' => ':env variable',
],
];

View file

@ -103,7 +103,7 @@
<div class="form-group">
<label for="pNodeId" class="form-label">Linked Node</label>
<select name="node_id" id="pNodeId" class="form-control">
<option value="null">None</option>
<option value="">None</option>
@foreach($locations as $location)
<optgroup label="{{ $location->short }}">
@foreach($location->nodes as $node)

View file

@ -148,7 +148,7 @@
text: item.name,
};
}),
}).change();
}).val(Pterodactyl.server.egg_id).change();
});
$('#pEggId').on('change', function (event) {
@ -157,8 +157,8 @@
$('#setDefaultImage').html(_.get(objectChain, 'docker_image', 'undefined'));
$('#pDockerImage').val(_.get(objectChain, 'docker_image', 'undefined'));
if (objectChain.id === parseInt('{{ $server->egg_id }}')) {
$('#pDockerImage').val('{{ $server->image }}');
if (objectChain.id === parseInt(Pterodactyl.server.egg_id)) {
$('#pDockerImage').val(Pterodactyl.server.image);
}
if (!_.get(objectChain, 'startup', false)) {
@ -178,9 +178,9 @@
),
});
@if(! is_null($server->pack_id))
$('#pPackId').val({{ $server->pack_id }});
@endif
if (Pterodactyl.server.pack_id !== null) {
$('#pPackId').val(Pterodactyl.server.pack_id);
}
$('#appendVariablesTo').html('');
$.each(_.get(objectChain, 'variables', []), function (i, item) {

View file

@ -133,7 +133,7 @@
<div class="row">
<div class="col-xs-12">
@if (count($errors) > 0)
<div class="callout callout-danger">
<div class="alert alert-danger">
@lang('base.validation_error')<br><br>
<ul>
@foreach ($errors->all() as $error)
@ -144,7 +144,7 @@
@endif
@foreach (Alert::getMessages() as $type => $messages)
@foreach ($messages as $message)
<div class="callout callout-{{ $type }} alert-dismissable" role="alert">
<div class="alert alert-{{ $type }} alert-dismissable" role="alert">
{!! $message !!}
</div>
@endforeach

View file

@ -215,7 +215,7 @@
<div class="row">
<div class="col-xs-12">
@if (count($errors) > 0)
<div class="callout callout-danger">
<div class="alert alert-danger">
@lang('base.validation_error')<br><br>
<ul>
@foreach ($errors->all() as $error)
@ -226,7 +226,7 @@
@endif
@foreach (Alert::getMessages() as $type => $messages)
@foreach ($messages as $message)
<div class="callout callout-{{ $type }} alert-dismissable" role="alert">
<div class="alert alert-{{ $type }} alert-dismissable" role="alert">
{!! $message !!}
</div>
@endforeach

View file

@ -1,11 +1,4 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Tests\Unit\Services\Servers;
@ -15,8 +8,7 @@ use Pterodactyl\Models\User;
use Illuminate\Support\Collection;
use Pterodactyl\Models\EggVariable;
use Illuminate\Contracts\Validation\Factory;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Exceptions\DisplayValidationException;
use Illuminate\Validation\ValidationException;
use Pterodactyl\Services\Servers\VariableValidatorService;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
@ -27,22 +19,17 @@ class VariableValidatorServiceTest extends TestCase
/**
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock
*/
protected $optionVariableRepository;
private $optionVariableRepository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
*/
protected $serverRepository;
private $serverRepository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface|\Mockery\Mock
*/
protected $serverVariableRepository;
/**
* @var \Illuminate\Contracts\Validation\Factory|\Mockery\Mock
*/
protected $validator;
private $serverVariableRepository;
/**
* Setup tests.
@ -54,7 +41,6 @@ class VariableValidatorServiceTest extends TestCase
$this->optionVariableRepository = m::mock(EggVariableRepositoryInterface::class);
$this->serverRepository = m::mock(ServerRepositoryInterface::class);
$this->serverVariableRepository = m::mock(ServerVariableRepositoryInterface::class);
$this->validator = m::mock(Factory::class);
}
/**
@ -77,13 +63,6 @@ class VariableValidatorServiceTest extends TestCase
$variables = $this->getVariableCollection();
$this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn($variables);
$this->validator->shouldReceive('make')->with([
'variable_value' => 'Test_SomeValue_0',
], [
'variable_value' => $variables[0]->rules,
])->once()->andReturnSelf();
$this->validator->shouldReceive('fails')->withNoArgs()->once()->andReturn(false);
$response = $this->getService()->handle(1, [
$variables[0]->env_variable => 'Test_SomeValue_0',
$variables[1]->env_variable => 'Test_SomeValue_1',
@ -112,15 +91,6 @@ class VariableValidatorServiceTest extends TestCase
$variables = $this->getVariableCollection();
$this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn($variables);
foreach ($variables as $key => $variable) {
$this->validator->shouldReceive('make')->with([
'variable_value' => 'Test_SomeValue_' . $key,
], [
'variable_value' => $variables[$key]->rules,
])->once()->andReturnSelf();
$this->validator->shouldReceive('fails')->withNoArgs()->once()->andReturn(false);
}
$service = $this->getService();
$service->setUserLevel(User::USER_LEVEL_ADMIN);
$response = $service->handle(1, [
@ -152,28 +122,16 @@ class VariableValidatorServiceTest extends TestCase
$variables = $this->getVariableCollection();
$this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn($variables);
$this->validator->shouldReceive('make')->with([
'variable_value' => null,
], [
'variable_value' => $variables[0]->rules,
])->once()->andReturnSelf();
$this->validator->shouldReceive('fails')->withNoArgs()->once()->andReturn(true);
$this->validator->shouldReceive('errors')->withNoArgs()->once()->andReturnSelf();
$this->validator->shouldReceive('toArray')->withNoArgs()->once()->andReturn([]);
try {
$this->getService()->handle(1, [$variables[0]->env_variable => null]);
} catch (PterodactylException $exception) {
$this->assertInstanceOf(DisplayValidationException::class, $exception);
} catch (ValidationException $exception) {
$messages = $exception->validator->getMessageBag()->all();
$decoded = json_decode($exception->getMessage());
$this->assertEquals(0, json_last_error(), 'Assert that response is decodable JSON.');
$this->assertObjectHasAttribute('notice', $decoded);
$this->assertEquals(
trans('admin/server.exceptions.bad_variable', ['name' => $variables[0]->name]),
$decoded->notice[0]
);
$this->assertNotEmpty($messages);
$this->assertSame(1, count($messages));
$this->assertSame(trans('validation.required', [
'attribute' => trans('validation.internal.variable_value', ['env' => $variables[0]->name]),
]), $messages[0]);
}
}
@ -205,7 +163,7 @@ class VariableValidatorServiceTest extends TestCase
$this->optionVariableRepository,
$this->serverRepository,
$this->serverVariableRepository,
$this->validator
$this->app->make(Factory::class)
);
}
}