[L6] Add support for custom model validation logic

This commit is contained in:
Dane Everitt 2019-09-04 22:19:57 -07:00
parent 5b4a65a60c
commit c586157dc4
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
19 changed files with 226 additions and 330 deletions

View file

@ -38,19 +38,10 @@ class Allocation extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'node_id' => 'required',
'ip' => 'required',
'port' => 'required',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'node_id' => 'exists:nodes,id',
'ip' => 'ip',
'port' => 'numeric|between:1024,65553',
public static $validationRules = [
'node_id' => 'required|exists:nodes,id',
'ip' => 'required|ip',
'port' => 'required|numeric|between:1024,65553',
'ip_alias' => 'nullable|string',
'server_id' => 'nullable|exists:servers,id',
];

View file

@ -74,30 +74,17 @@ class ApiKey extends Validable
*/
protected $hidden = ['token'];
/**
* Rules defining what fields must be passed when making a model.
*
* @var array
*/
protected static $applicationRules = [
'identifier' => 'required',
'memo' => 'required',
'user_id' => 'required',
'token' => 'required',
'key_type' => 'present',
];
/**
* Rules to protect against invalid data entry to DB.
*
* @var array
*/
protected static $dataIntegrityRules = [
'user_id' => 'exists:users,id',
'key_type' => 'integer|min:0|max:4',
'identifier' => 'string|size:16|unique:api_keys,identifier',
'token' => 'string',
'memo' => 'nullable|string|max:500',
public static $validationRules = [
'user_id' => 'required|exists:users,id',
'key_type' => 'present|integer|min:0|max:4',
'identifier' => 'required|string|size:16|unique:api_keys,identifier',
'token' => 'required|string',
'memo' => 'required|nullable|string|max:500',
'allowed_ips' => 'nullable|json',
'last_used_at' => 'nullable|date',
'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3',

View file

@ -38,21 +38,11 @@ class DaemonKey extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'user_id' => 'required',
'server_id' => 'required',
'secret' => 'required',
'expires_at' => 'required',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'user_id' => 'numeric|exists:users,id',
'server_id' => 'numeric|exists:servers,id',
'secret' => 'string|min:20',
'expires_at' => 'date',
public static $validationRules = [
'user_id' => 'required|numeric|exists:users,id',
'server_id' => 'required|numeric|exists:servers,id',
'secret' => 'required|string|min:20',
'expires_at' => 'required|date',
];
/**

View file

@ -43,19 +43,15 @@ class Database extends Validable
'database_host_id' => 'integer',
];
protected static $applicationRules = [
'server_id' => 'required',
'database_host_id' => 'required',
'database' => 'required',
'remote' => 'required',
];
protected static $dataIntegrityRules = [
'server_id' => 'numeric|exists:servers,id',
'database_host_id' => 'exists:database_hosts,id',
'database' => 'string|alpha_dash|between:3,100',
/**
* @var array
*/
public static $validationRules = [
'server_id' => 'required|numeric|exists:servers,id',
'database_host_id' => 'required|exists:database_hosts,id',
'database' => 'required|string|alpha_dash|between:3,100',
'username' => 'string|alpha_dash|between:3,100',
'remote' => 'string|regex:/^[0-9%.]{1,15}$/',
'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/',
'password' => 'string',
];

View file

@ -44,31 +44,18 @@ class DatabaseHost extends Validable
'node_id' => 'integer',
];
/**
* Application validation rules.
*
* @var array
*/
protected static $applicationRules = [
'name' => 'required',
'host' => 'required',
'port' => 'required',
'username' => 'required',
'node_id' => 'sometimes',
];
/**
* Validation rules to assign to this model.
*
* @var array
*/
protected static $dataIntegrityRules = [
'name' => 'string|max:255',
'host' => 'ip|unique:database_hosts,host',
'port' => 'numeric|between:1,65535',
'username' => 'string|max:32',
public static $validationRules = [
'name' => 'required|string|max:255',
'host' => 'required|ip|unique:database_hosts,host',
'port' => 'required|numeric|between:1,65535',
'username' => 'required|string|max:32',
'password' => 'nullable|string',
'node_id' => 'nullable|integer|exists:nodes,id',
'node_id' => 'sometimes|nullable|integer|exists:nodes,id',
];
/**

View file

@ -54,37 +54,19 @@ class Egg extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'nest_id' => 'required',
'uuid' => 'required',
'name' => 'required',
'description' => 'required',
'author' => 'required',
'docker_image' => 'required',
'startup' => 'required',
'config_from' => 'sometimes',
'config_stop' => 'required_without:config_from',
'config_startup' => 'required_without:config_from',
'config_logs' => 'required_without:config_from',
'config_files' => 'required_without:config_from',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'nest_id' => 'bail|numeric|exists:nests,id',
'uuid' => 'string|size:36',
'name' => 'string|max:255',
'description' => 'string',
'author' => 'string|email',
'docker_image' => 'string|max:255',
'startup' => 'nullable|string',
'config_from' => 'bail|nullable|numeric|exists:eggs,id',
'config_stop' => 'nullable|string|max:255',
'config_startup' => 'nullable|json',
'config_logs' => 'nullable|json',
'config_files' => 'nullable|json',
public static $validationRules = [
'nest_id' => 'required|bail|numeric|exists:nests,id',
'uuid' => 'required|string|size:36',
'name' => 'required|string|max:255',
'description' => 'required|string',
'author' => 'required|string|email',
'docker_image' => 'required|string|max:255',
'startup' => 'required|nullable|string',
'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id',
'config_stop' => 'required_without:config_from|nullable|string|max:255',
'config_startup' => 'required_without:config_from|nullable|json',
'config_logs' => 'required_without:config_from|nullable|json',
'config_files' => 'required_without:config_from|nullable|json',
];
/**

View file

@ -45,24 +45,15 @@ class EggVariable extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'name' => 'required',
'env_variable' => 'required',
'rules' => 'required',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
public static $validationRules = [
'egg_id' => 'exists:eggs,id',
'name' => 'string|between:1,255',
'name' => 'required|string|between:1,255',
'description' => 'string',
'env_variable' => 'regex:/^[\w]{1,255}$/|notIn:' . self::RESERVED_ENV_NAMES,
'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . self::RESERVED_ENV_NAMES,
'default_value' => 'string',
'user_viewable' => 'boolean',
'user_editable' => 'boolean',
'rules' => 'string',
'rules' => 'required|string',
];
/**

View file

@ -24,24 +24,14 @@ class Location extends Validable
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Validation rules to apply to this model.
*
* @var array
*/
protected static $applicationRules = [
'short' => 'required',
'long' => 'required',
];
/**
* Rules ensuring that the raw data stored in the database meets expectations.
*
* @var array
*/
protected static $dataIntegrityRules = [
'short' => 'string|between:1,60|unique:locations,short',
'long' => 'string|between:1,255',
public static $validationRules = [
'short' => 'required|string|between:1,60|unique:locations,short',
'long' => 'required|string|between:1,255',
];
/**

View file

@ -30,19 +30,10 @@ class Nest extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'author' => 'required',
'name' => 'required',
'description' => 'sometimes',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'author' => 'string|email',
'name' => 'string|max:255',
'description' => 'nullable|string',
public static $validationRules = [
'author' => 'required|string|email',
'name' => 'required|string|max:255',
'description' => 'sometimes|nullable|string',
];
/**

View file

@ -77,37 +77,21 @@ class Node extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'name' => 'required',
'location_id' => 'required',
'fqdn' => 'required',
'scheme' => 'required',
'memory' => 'required',
'memory_overallocate' => 'required',
'disk' => 'required',
'disk_overallocate' => 'required',
'daemonBase' => 'sometimes|required',
'daemonSFTP' => 'required',
'daemonListen' => 'required',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'name' => 'regex:/^([\w .-]{1,100})$/',
public static $validationRules = [
'name' => 'required|regex:/^([\w .-]{1,100})$/',
'description' => 'string',
'location_id' => 'exists:locations,id',
'location_id' => 'required|exists:locations,id',
'public' => 'boolean',
'fqdn' => 'string',
'fqdn' => 'required|string',
'scheme' => 'required',
'behind_proxy' => 'boolean',
'memory' => 'numeric|min:1',
'memory_overallocate' => 'numeric|min:-1',
'disk' => 'numeric|min:1',
'disk_overallocate' => 'numeric|min:-1',
'daemonBase' => 'regex:/^([\/][\d\w.\-\/]+)$/',
'daemonSFTP' => 'numeric|between:1,65535',
'daemonListen' => 'numeric|between:1,65535',
'memory' => 'required|numeric|min:1',
'memory_overallocate' => 'required|numeric|min:-1',
'disk' => 'required|numeric|min:1',
'disk_overallocate' => 'required|numeric|min:-1',
'daemonBase' => 'sometimes|required|regex:/^([\/][\d\w.\-\/]+)$/',
'daemonSFTP' => 'required|numeric|between:1,65535',
'daemonListen' => 'required|numeric|between:1,65535',
'maintenance_mode' => 'boolean',
'upload_size' => 'int|between:1,1024',
];

View file

@ -33,27 +33,14 @@ class Pack extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'name' => 'required',
'version' => 'required',
'description' => 'sometimes',
'selectable' => 'sometimes|required',
'visible' => 'sometimes|required',
'locked' => 'sometimes|required',
'egg_id' => 'required',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'name' => 'string',
'version' => 'string',
'description' => 'nullable|string',
'selectable' => 'boolean',
'visible' => 'boolean',
'locked' => 'boolean',
'egg_id' => 'exists:eggs,id',
public static $validationRules = [
'name' => 'required|string',
'version' => 'required|string',
'description' => 'sometimes|nullable|string',
'selectable' => 'sometimes|required|boolean',
'visible' => 'sometimes|required|boolean',
'locked' => 'sometimes|required|boolean',
'egg_id' => 'required|exists:eggs,id',
];
/**

View file

@ -43,17 +43,9 @@ class Permission extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'subuser_id' => 'required',
'permission' => 'required',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'subuser_id' => 'numeric|min:1',
'permission' => 'string',
public static $validationRules = [
'subuser_id' => 'required|numeric|min:1',
'permission' => 'required|string',
];
/**

View file

@ -73,24 +73,13 @@ class Schedule extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'server_id' => 'required',
'cron_day_of_week' => 'required',
'cron_day_of_month' => 'required',
'cron_hour' => 'required',
'cron_minute' => 'required',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'server_id' => 'exists:servers,id',
public static $validationRules = [
'server_id' => 'required|exists:servers,id',
'name' => 'nullable|string|max:255',
'cron_day_of_week' => 'string',
'cron_day_of_month' => 'string',
'cron_hour' => 'string',
'cron_minute' => 'string',
'cron_day_of_week' => 'required|string',
'cron_day_of_month' => 'required|string',
'cron_hour' => 'required|string',
'cron_minute' => 'required|string',
'is_active' => 'boolean',
'is_processing' => 'boolean',
'last_run_at' => 'nullable|date',

View file

@ -95,53 +95,28 @@ class Server extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'external_id' => 'sometimes',
'owner_id' => 'required',
'name' => 'required',
'memory' => 'required',
'swap' => 'required',
'io' => 'required',
'cpu' => 'required',
'oom_disabled' => 'sometimes',
'disk' => 'required',
'nest_id' => 'required',
'egg_id' => 'required',
'node_id' => 'required',
'allocation_id' => 'required',
'pack_id' => 'sometimes',
'skip_scripts' => 'sometimes',
'image' => 'required',
'startup' => 'required',
'database_limit' => 'present',
'allocation_limit' => 'sometimes',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'external_id' => 'nullable|string|between:1,191|unique:servers',
'owner_id' => 'integer|exists:users,id',
'name' => 'string|min:1|max:255',
'node_id' => 'exists:nodes,id',
public static $validationRules = [
'external_id' => 'sometimes|nullable|string|between:1,191|unique:servers',
'owner_id' => 'required|integer|exists:users,id',
'name' => 'required|string|min:1|max:255',
'node_id' => 'required|exists:nodes,id',
'description' => 'string',
'memory' => 'numeric|min:0',
'swap' => 'numeric|min:-1',
'io' => 'numeric|between:10,1000',
'cpu' => 'numeric|min:0',
'oom_disabled' => 'boolean',
'disk' => 'numeric|min:0',
'allocation_id' => 'bail|unique:servers|exists:allocations,id',
'nest_id' => 'exists:nests,id',
'egg_id' => 'exists:eggs,id',
'pack_id' => 'nullable|numeric|min:0',
'startup' => 'string',
'skip_scripts' => 'boolean',
'image' => 'string|max:255',
'memory' => 'required|numeric|min:0',
'swap' => 'required|numeric|min:-1',
'io' => 'required|numeric|between:10,1000',
'cpu' => 'required|numeric|min:0',
'oom_disabled' => 'sometimes|boolean',
'disk' => 'required|numeric|min:0',
'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
'nest_id' => 'required|exists:nests,id',
'egg_id' => 'required|exists:eggs,id',
'pack_id' => 'sometimes|nullable|numeric|min:0',
'startup' => 'required|string',
'skip_scripts' => 'sometimes|boolean',
'image' => 'required|string|max:255',
'installed' => 'in:0,1,2',
'database_limit' => 'nullable|integer|min:0',
'allocation_limit' => 'nullable|integer|min:0',
'database_limit' => 'present|nullable|integer|min:0',
'allocation_limit' => 'sometimes|nullable|integer|min:0',
];
/**

View file

@ -24,7 +24,7 @@ class Setting extends Validable
/**
* @var array
*/
protected static $applicationRules = [
public static $validationRules = [
'key' => 'required|string|between:1,255',
'value' => 'string',
];

View file

@ -41,17 +41,9 @@ class Subuser extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'user_id' => 'required',
'server_id' => 'required',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'user_id' => 'numeric|exists:users,id',
'server_id' => 'numeric|exists:servers,id',
public static $validationRules = [
'user_id' => 'required|numeric|exists:users,id',
'server_id' => 'required|numeric|exists:servers,id',
];
/**

View file

@ -67,23 +67,12 @@ class Task extends Validable
/**
* @var array
*/
protected static $applicationRules = [
'schedule_id' => 'required',
'sequence_id' => 'required',
'action' => 'required',
'payload' => 'required',
'time_offset' => 'required',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'schedule_id' => 'numeric|exists:schedules,id',
'sequence_id' => 'numeric|min:1',
'action' => 'string',
'payload' => 'string',
'time_offset' => 'numeric|between:0,900',
public static $validationRules = [
'schedule_id' => 'required|numeric|exists:schedules,id',
'sequence_id' => 'required|numeric|min:1',
'action' => 'required|string',
'payload' => 'required|string',
'time_offset' => 'required|numeric|between:0,900',
'is_queued' => 'boolean',
];

View file

@ -121,38 +121,21 @@ class User extends Validable implements
'totp_secret' => null,
];
/**
* Rules verifying that the data passed in forms is valid and meets application logic rules.
*
* @var array
*/
protected static $applicationRules = [
'uuid' => 'required',
'email' => 'required',
'external_id' => 'sometimes',
'username' => 'required',
'name_first' => 'required',
'name_last' => 'required',
'password' => 'sometimes',
'language' => 'sometimes',
'use_totp' => 'sometimes',
];
/**
* Rules verifying that the data being stored matches the expectations of the database.
*
* @var array
*/
protected static $dataIntegrityRules = [
'uuid' => 'string|size:36|unique:users,uuid',
'email' => 'email|unique:users,email',
'external_id' => 'nullable|string|max:255|unique:users,external_id',
'username' => 'between:1,255|unique:users,username',
'name_first' => 'string|between:1,255',
'name_last' => 'string|between:1,255',
'password' => 'nullable|string',
public static $validationRules = [
'uuid' => 'required|string|size:36|unique:users,uuid',
'email' => 'required|email|unique:users,email',
'external_id' => 'sometimes|nullable|string|max:255|unique:users,external_id',
'username' => 'required|between:1,255|unique:users,username',
'name_first' => 'required|string|between:1,255',
'name_last' => 'required|string|between:1,255',
'password' => 'required|nullable|string',
'root_admin' => 'boolean',
'language' => 'string',
'language' => 'required|string',
'use_totp' => 'boolean',
'totp_secret' => 'nullable|string',
];

View file

@ -2,39 +2,139 @@
namespace Pterodactyl\Models;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Validation\Factory;
abstract class Validable extends Model
{
/**
* @var array
* Determines if the model should undergo data validation before it is saved
* to the database.
*
* @var bool
*/
protected static $applicationRules = [];
protected $skipValidation = false;
/**
* The validator instance used by this model.
*
* @var \Illuminate\Validation\Validator
*/
protected $validator;
/**
* @var \Illuminate\Contracts\Validation\Factory
*/
protected static $validatorFactory;
/**
* @var array
*/
protected static $dataIntegrityRules = [];
public static $validationRules = [];
/**
* Listen for the model saving event and fire off the validation
* function before it is saved.
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected static function boot()
{
parent::boot();
static::$validatorFactory = Container::getInstance()->make(Factory::class);
static::saving(function (Validable $model) {
return $model->validate();
});
}
/**
* @todo implement custom logic once L6 is done
* Set the model to skip validation when saving.
*
* @return $this
*/
public function skipValidation()
{
$this->skipValidation = true;
return $this;
}
/**
* Returns the validator instance used by this model.
*
* @return \Illuminate\Validation\Validator|\Illuminate\Contracts\Validation\Validator
*/
public function getValidator()
{
$rules = $this->getKey() ? static::getRulesForUpdate($this) : static::getRules();
return $this->validator ?: $this->validator = static::$validatorFactory->make(
[], $rules, [], []
);
}
/**
* @return array
*/
public static function getRules()
{
return static::$validationRules;
}
/**
* @param \Illuminate\Database\Eloquent\Model|int|string $id
* @param string $primaryKey
* @return array
*/
public static function getRulesForUpdate($id, string $primaryKey = 'id')
{
if ($id instanceof Model) {
list($primaryKey, $id) = [$id->getKeyName(), $id->getKey()];
}
$rules = static::getRules();
foreach ($rules as $key => &$rule) {
$rule = is_array($rule) ? $rule : explode('|', $rule);
}
foreach ($rules as $key => &$data) {
// For each rule in a given field, iterate over it and confirm if the rule
// is one for a unique field. If that is the case, append the ID of the current
// working model so we don't run into errors due to the way that field validation
// works.
foreach ($data as &$datum) {
if (! Str::startsWith($datum, 'unique')) {
continue;
}
list(, $args) = explode(':', $datum);
$args = explode(',', $args);
$datum = Rule::unique($args[0], $args[1] ?? $key)->ignore($id, $primaryKey)->__toString();
}
}
return $rules;
}
/**
* Determines if the model is in a valid state or not.
*
* @return bool
*/
public function validate()
{
return true;
if ($this->skipValidation) {
return true;
}
return $this->getValidator()->setData(
$this->getAttributes()
)->passes();
}
}