Skip to content

Commit b96c2d1

Browse files
committed
Added validation to variable validation rules to validate that the validation rules are valid
closes pterodactyl#988
1 parent 7a04a9f commit b96c2d1

File tree

9 files changed

+246
-32
lines changed

9 files changed

+246
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
99

1010
### Added
1111
* Added a new client API endpoint for gathering the utilization stats for servers including disk, cpu, and memory. `GET /api/client/servers/<id>/utilization`
12+
* Added validation to variable validation rules to validate that the validation rules are valid because we heard you like validating your validation.
1213

1314
## v0.7.6 (Derelict Dermodactylus)
1415
### Fixed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Pterodactyl\Exceptions\Service\Egg\Variable;
4+
5+
use Pterodactyl\Exceptions\DisplayException;
6+
7+
class BadValidationRuleException extends DisplayException
8+
{
9+
}

app/Services/Eggs/Variables/VariableCreationService.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,46 @@
33
namespace Pterodactyl\Services\Eggs\Variables;
44

55
use Pterodactyl\Models\EggVariable;
6+
use Illuminate\Contracts\Validation\Factory;
7+
use Pterodactyl\Traits\Services\ValidatesValidationRules;
68
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
79
use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException;
810

911
class VariableCreationService
1012
{
13+
use ValidatesValidationRules;
14+
1115
/**
1216
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface
1317
*/
14-
protected $repository;
18+
private $repository;
19+
20+
/**
21+
* @var \Illuminate\Contracts\Validation\Factory
22+
*/
23+
private $validator;
1524

1625
/**
1726
* VariableCreationService constructor.
1827
*
1928
* @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $repository
29+
* @param \Illuminate\Contracts\Validation\Factory $validator
2030
*/
21-
public function __construct(EggVariableRepositoryInterface $repository)
31+
public function __construct(EggVariableRepositoryInterface $repository, Factory $validator)
2232
{
2333
$this->repository = $repository;
34+
$this->validator = $validator;
35+
}
36+
37+
/**
38+
* Return the validation factory instance to be used by rule validation
39+
* checking in the trait.
40+
*
41+
* @return \Illuminate\Contracts\Validation\Factory
42+
*/
43+
protected function getValidator(): Factory
44+
{
45+
return $this->validator;
2446
}
2547

2648
/**
@@ -31,6 +53,7 @@ public function __construct(EggVariableRepositoryInterface $repository)
3153
* @return \Pterodactyl\Models\EggVariable
3254
*
3355
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
56+
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
3457
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
3558
*/
3659
public function handle(int $egg, array $data): EggVariable
@@ -42,6 +65,10 @@ public function handle(int $egg, array $data): EggVariable
4265
));
4366
}
4467

68+
if (! empty($data['rules'] ?? '')) {
69+
$this->validateRules($data['rules']);
70+
}
71+
4572
$options = array_get($data, 'options') ?? [];
4673

4774
return $this->repository->create([

app/Services/Eggs/Variables/VariableUpdateService.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,47 @@
33
namespace Pterodactyl\Services\Eggs\Variables;
44

55
use Pterodactyl\Models\EggVariable;
6+
use Illuminate\Contracts\Validation\Factory;
67
use Pterodactyl\Exceptions\DisplayException;
8+
use Pterodactyl\Traits\Services\ValidatesValidationRules;
79
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
810
use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException;
911

1012
class VariableUpdateService
1113
{
14+
use ValidatesValidationRules;
15+
1216
/**
1317
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface
1418
*/
15-
protected $repository;
19+
private $repository;
20+
21+
/**
22+
* @var \Illuminate\Contracts\Validation\Factory
23+
*/
24+
private $validator;
1625

1726
/**
1827
* VariableUpdateService constructor.
1928
*
2029
* @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $repository
30+
* @param \Illuminate\Contracts\Validation\Factory $validator
2131
*/
22-
public function __construct(EggVariableRepositoryInterface $repository)
32+
public function __construct(EggVariableRepositoryInterface $repository, Factory $validator)
2333
{
2434
$this->repository = $repository;
35+
$this->validator = $validator;
36+
}
37+
38+
/**
39+
* Return the validation factory instance to be used by rule validation
40+
* checking in the trait.
41+
*
42+
* @return \Illuminate\Contracts\Validation\Factory
43+
*/
44+
protected function getValidator(): Factory
45+
{
46+
return $this->validator;
2547
}
2648

2749
/**
@@ -58,6 +80,10 @@ public function handle(EggVariable $variable, array $data)
5880
}
5981
}
6082

83+
if (! empty($data['rules'] ?? '')) {
84+
$this->validateRules($data['rules']);
85+
}
86+
6187
$options = array_get($data, 'options') ?? [];
6288

6389
return $this->repository->withoutFreshModel()->update($variable->id, [
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Pterodactyl\Traits\Services;
4+
5+
use BadMethodCallException;
6+
use Illuminate\Support\Str;
7+
use Illuminate\Contracts\Validation\Factory;
8+
use Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException;
9+
10+
trait ValidatesValidationRules
11+
{
12+
/**
13+
* @return \Illuminate\Contracts\Validation\Factory
14+
*/
15+
abstract protected function getValidator(): Factory;
16+
17+
/**
18+
* Validate that the rules being provided are valid for Laravel and can
19+
* be resolved.
20+
*
21+
* @param array|string $rules
22+
*
23+
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
24+
*/
25+
public function validateRules($rules)
26+
{
27+
try {
28+
$this->getValidator()->make(['__TEST' => 'test'], ['__TEST' => $rules])->fails();
29+
} catch (BadMethodCallException $exception) {
30+
$matches = [];
31+
if (preg_match('/Method \[(.+)\] does not exist\./', $exception->getMessage(), $matches)) {
32+
throw new BadValidationRuleException(trans('exceptions.nest.variables.bad_validation_rule', [
33+
'rule' => Str::snake(str_replace('validate', '', array_get($matches, 1, 'unknownRule'))),
34+
]), $exception);
35+
}
36+
37+
throw $exception;
38+
}
39+
}
40+
}

resources/lang/en/exceptions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
'variables' => [
2525
'env_not_unique' => 'The environment variable :name must be unique to this Egg.',
2626
'reserved_name' => 'The environment variable :name is protected and cannot be assigned to a variable.',
27+
'bad_validation_rule' => 'The validation rule ":rule" is not a valid rule for this application.',
2728
],
2829
'importer' => [
2930
'json_error' => 'There was an error while attempting to parse the JSON file: :error.',

resources/themes/pterodactyl/admin/eggs/variables.blade.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,20 +105,20 @@
105105
<div class="modal-body">
106106
<div class="form-group">
107107
<label class="control-label">Name <span class="field-required"></span></label>
108-
<input type="text" name="name" class="form-control" />
108+
<input type="text" name="name" class="form-control" value="{{ old('name') }}"/>
109109
</div>
110110
<div class="form-group">
111111
<label class="control-label">Description</label>
112-
<textarea name="description" class="form-control" rows="3"></textarea>
112+
<textarea name="description" class="form-control" rows="3">{{ old('description') }}</textarea>
113113
</div>
114114
<div class="row">
115115
<div class="form-group col-md-6">
116116
<label class="control-label">Environment Variable <span class="field-required"></span></label>
117-
<input type="text" name="env_variable" class="form-control" />
117+
<input type="text" name="env_variable" class="form-control" value="{{ old('env_variable') }}" />
118118
</div>
119119
<div class="form-group col-md-6">
120120
<label class="control-label">Default Value</label>
121-
<input type="text" name="default_value" class="form-control" />
121+
<input type="text" name="default_value" class="form-control" value="{{ old('default_value') }}" />
122122
</div>
123123
<div class="col-xs-12">
124124
<p class="text-muted small">This variable can be accessed in the statup command by entering <code>@{{environment variable value}}</code>.</p>
@@ -133,7 +133,7 @@
133133
</div>
134134
<div class="form-group">
135135
<label class="control-label">Input Rules <span class="field-required"></span></label>
136-
<input type="text" name="rules" class="form-control" value="required|string|max:20" placeholder="required|string|max:20" />
136+
<input type="text" name="rules" class="form-control" value="{{ old('rules', 'required|string|max:20') }}" placeholder="required|string|max:20" />
137137
<p class="text-muted small">These rules are defined using standard Laravel Framework validation rules.</p>
138138
</div>
139139
</div>

tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
use Mockery as m;
66
use Tests\TestCase;
7+
use BadMethodCallException;
78
use Pterodactyl\Models\EggVariable;
9+
use Illuminate\Contracts\Validation\Factory;
810
use Pterodactyl\Services\Eggs\Variables\VariableCreationService;
911
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
1012

@@ -13,12 +15,12 @@ class VariableCreationServiceTest extends TestCase
1315
/**
1416
* @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock
1517
*/
16-
protected $repository;
18+
private $repository;
1719

1820
/**
19-
* @var \Pterodactyl\Services\Eggs\Variables\VariableCreationService
21+
* @var \Illuminate\Contracts\Validation\Factory|\Mockery\Mock
2022
*/
21-
protected $service;
23+
private $validator;
2224

2325
/**
2426
* Setup tests.
@@ -28,8 +30,7 @@ public function setUp()
2830
parent::setUp();
2931

3032
$this->repository = m::mock(EggVariableRepositoryInterface::class);
31-
32-
$this->service = new VariableCreationService($this->repository);
33+
$this->validator = m::mock(Factory::class);
3334
}
3435

3536
/**
@@ -46,7 +47,7 @@ public function testVariableIsCreatedAndStored()
4647
'env_variable' => 'TEST_VAR_123',
4748
]))->once()->andReturn(new EggVariable);
4849

49-
$this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data));
50+
$this->assertInstanceOf(EggVariable::class, $this->getService()->handle(1, $data));
5051
}
5152

5253
/**
@@ -62,7 +63,7 @@ public function testOptionsPassedInArrayKeyAreParsedProperly()
6263
'env_variable' => 'TEST_VAR_123',
6364
]))->once()->andReturn(new EggVariable);
6465

65-
$this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data));
66+
$this->assertInstanceOf(EggVariable::class, $this->getService()->handle(1, $data));
6667
}
6768

6869
/**
@@ -81,18 +82,20 @@ public function testNullOptionValueIsPassedAsArray()
8182
'user_editable' => false,
8283
]))->once()->andReturn(new EggVariable);
8384

84-
$this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data));
85+
$this->assertInstanceOf(EggVariable::class, $this->getService()->handle(1, $data));
8586
}
8687

8788
/**
8889
* Test that all of the reserved variables defined in the model trigger an exception.
8990
*
91+
* @param string $variable
92+
*
9093
* @dataProvider reservedNamesProvider
9194
* @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
9295
*/
9396
public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames(string $variable)
9497
{
95-
$this->service->handle(1, ['env_variable' => $variable]);
98+
$this->getService()->handle(1, ['env_variable' => $variable]);
9699
}
97100

98101
/**
@@ -106,7 +109,49 @@ public function testEggIdPassedInDataIsNotApplied()
106109
'egg_id' => 1,
107110
]))->once()->andReturn(new EggVariable);
108111

109-
$this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data));
112+
$this->assertInstanceOf(EggVariable::class, $this->getService()->handle(1, $data));
113+
}
114+
115+
/**
116+
* Test that validation errors due to invalid rules are caught and handled properly.
117+
*
118+
* @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
119+
* @expectedExceptionMessage The validation rule "hodor_door" is not a valid rule for this application.
120+
*/
121+
public function testInvalidValidationRulesResultInException()
122+
{
123+
$data = ['env_variable' => 'TEST_VAR_123', 'rules' => 'string|hodorDoor'];
124+
125+
$this->validator->shouldReceive('make')->once()
126+
->with(['__TEST' => 'test'], ['__TEST' => 'string|hodorDoor'])
127+
->andReturnSelf();
128+
129+
$this->validator->shouldReceive('fails')->once()
130+
->withNoArgs()
131+
->andThrow(new BadMethodCallException('Method [validateHodorDoor] does not exist.'));
132+
133+
$this->getService()->handle(1, $data);
134+
}
135+
136+
/**
137+
* Test that an exception not stemming from a bad rule is not caught.
138+
*
139+
* @expectedException \BadMethodCallException
140+
* @expectedExceptionMessage Received something, but no expectations were specified.
141+
*/
142+
public function testExceptionNotCausedByBadRuleIsNotCaught()
143+
{
144+
$data = ['env_variable' => 'TEST_VAR_123', 'rules' => 'string'];
145+
146+
$this->validator->shouldReceive('make')->once()
147+
->with(['__TEST' => 'test'], ['__TEST' => 'string'])
148+
->andReturnSelf();
149+
150+
$this->validator->shouldReceive('fails')->once()
151+
->withNoArgs()
152+
->andThrow(new BadMethodCallException('Received something, but no expectations were specified.'));
153+
154+
$this->getService()->handle(1, $data);
110155
}
111156

112157
/**
@@ -124,4 +169,14 @@ public function reservedNamesProvider()
124169

125170
return $data;
126171
}
172+
173+
/**
174+
* Return an instance of the service with mocked dependencies for testing.
175+
*
176+
* @return \Pterodactyl\Services\Eggs\Variables\VariableCreationService
177+
*/
178+
private function getService(): VariableCreationService
179+
{
180+
return new VariableCreationService($this->repository, $this->validator);
181+
}
127182
}

0 commit comments

Comments
 (0)