api(application): v2 backport

This commit is contained in:
Matthew Penner 2022-12-14 17:05:46 -07:00
parent 4cd0bee231
commit 67bf3e342e
No known key found for this signature in database
172 changed files with 2922 additions and 1579 deletions

View file

@ -0,0 +1,83 @@
<?php
namespace Pterodactyl\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* @method \Pterodactyl\Models\User user($guard = null)
*/
abstract class ApiRequest extends FormRequest
{
/**
* Tracks if the request has been validated internally or not to avoid
* making duplicate validation calls.
*/
private bool $hasValidated = false;
/**
* Determine if the current user is authorized to perform the requested
* action against the API.
*/
public function authorize(): bool
{
return false;
}
/**
* Default set of rules to apply to API requests.
*/
public function rules(): array
{
return [];
}
/**
* Validate that the resource exists and can be accessed prior to booting
* the validator and attempting to use the data.
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
protected function prepareForValidation()
{
if (!$this->passesAuthorization()) {
$this->failedAuthorization();
}
$this->hasValidated = true;
}
/*
* Determine if the request passes the authorization check as well
* as the exists check.
*
* @return bool
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function passesAuthorization()
{
// If we have already validated we do not need to call this function
// again. This is needed to work around Laravel's normal auth validation
// that occurs after validating the request params since we are doing auth
// validation in the prepareForValidation() function.
if ($this->hasValidated) {
return true;
}
if (!parent::passesAuthorization()) {
return false;
}
// Only let the user know that a resource does not exist if they are
// authenticated to access the endpoint. This avoids exposing that
// an item exists (or does not exist) to the user until they can prove
// that they have permission to know about it.
if ($this->attributes->get('is_missing_model', false)) {
throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
}
return true;
}
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Allocations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteAllocationRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS;
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Allocations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetAllocationsRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,15 +2,11 @@
namespace Pterodactyl\Http\Requests\Api\Application\Allocations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Illuminate\Support\Arr;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreAllocationRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS;
protected int $permission = AdminAcl::WRITE;
public function rules(): array
{
return [
@ -21,14 +17,22 @@ class StoreAllocationRequest extends ApplicationApiRequest
];
}
public function validated($key = null, $default = null): array
/**
* @param string|null $key
* @param string|array|null $default
*
* @return mixed
*/
public function validated($key = null, $default = null)
{
$data = parent::validated();
return [
$response = [
'allocation_ip' => $data['ip'],
'allocation_ports' => $data['ports'],
'allocation_alias' => $data['alias'] ?? null,
];
return is_null($key) ? $response : Arr::get($response, $key, $default);
}
}

View file

@ -2,94 +2,16 @@
namespace Pterodactyl\Http\Requests\Api\Application;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\ApiKey;
use Laravel\Sanctum\TransientToken;
use Illuminate\Validation\Validator;
use Illuminate\Database\Eloquent\Model;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Illuminate\Foundation\Http\FormRequest;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Http\Requests\Api\ApiRequest;
abstract class ApplicationApiRequest extends FormRequest
abstract class ApplicationApiRequest extends ApiRequest
{
/**
* The resource that should be checked when performing the authorization
* function for this request.
*/
protected ?string $resource;
/**
* The permission level that a given API key should have for accessing
* the defined $resource during the request cycle.
*/
protected int $permission = AdminAcl::NONE;
/**
* Determine if the current user is authorized to perform
* the requested action against the API.
*
* @throws PterodactylException
* This will eventually be replaced with per-request permissions checking
* on the API key and for the user.
*/
public function authorize(): bool
{
if (is_null($this->resource)) {
throw new PterodactylException('An ACL resource must be defined on API requests.');
}
$token = $this->user()->currentAccessToken();
if ($token instanceof TransientToken) {
return true;
}
/** @var ApiKey $token */
if ($token->key_type === ApiKey::TYPE_ACCOUNT) {
return true;
}
return AdminAcl::check($token, $this->resource, $this->permission);
}
/**
* Default set of rules to apply to API requests.
*/
public function rules(): array
{
return [];
}
/**
* Helper method allowing a developer to easily hook into this logic without having
* to remember what the method name is called or where to use it. By default this is
* a no-op.
*/
public function withValidator(Validator $validator): void
{
// do nothing
}
/**
* Returns the named route parameter and asserts that it is a real model that
* exists in the database.
*
* @template T of \Illuminate\Database\Eloquent\Model
*
* @param class-string<T> $expect
*
* @return T
*
* @noinspection PhpDocSignatureInspection
*/
public function parameter(string $key, string $expect)
{
/** @var ApiKey $value */
$value = $this->route()->parameter($key);
Assert::isInstanceOf($value, $expect);
Assert::isInstanceOf($value, Model::class);
Assert::true($value->exists);
/* @var T $value */
return $value;
return $this->user()->root_admin;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteDatabaseRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
class GetDatabaseRequest extends GetDatabasesRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetDatabasesRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,14 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
use Pterodactyl\Models\DatabaseHost;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreDatabaseRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? DatabaseHost::getRules();
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
use Pterodactyl\Models\DatabaseHost;
class UpdateDatabaseRequest extends StoreDatabaseRequest
{
public function rules(array $rules = null): array
{
return $rules ?? DatabaseHost::getRulesForUpdate($this->route()->parameter('databaseHost')->id);
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Models\Egg;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteEggRequest extends ApplicationApiRequest
{
public function resourceExists(): bool
{
$egg = $this->route()->parameter('egg');
return $egg instanceof Egg && $egg->exists;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class ExportEggRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
class GetEggRequest extends GetEggsRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetEggsRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class ImportEggRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,30 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreEggRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return [
'nest_id' => 'required|bail|numeric|exists:nests,id',
'name' => 'required|string|max:191',
'description' => 'sometimes|string|nullable',
'features' => 'sometimes|array',
'docker_images' => 'required|array|min:1',
'docker_images.*' => 'required|string',
'file_denylist' => 'sometimes|array|nullable',
'file_denylist.*' => 'sometimes|string',
'config_files' => 'required|nullable|json',
'config_startup' => 'required|nullable|json',
'config_stop' => 'required|nullable|string|max:191',
// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id',
'startup' => 'required|string',
'script_container' => 'sometimes|string',
'script_entry' => 'sometimes|string',
'script_install' => 'sometimes|string',
];
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
class UpdateEggRequest extends StoreEggRequest
{
public function rules(array $rules = null): array
{
return [
'nest_id' => 'sometimes|numeric|exists:nests,id',
'name' => 'sometimes|string|max:191',
'description' => 'sometimes|string|nullable',
'features' => 'sometimes|array',
'docker_images' => 'sometimes|array|min:1',
'docker_images.*' => 'sometimes|string',
'file_denylist' => 'sometimes|array|nullable',
'file_denylist.*' => 'sometimes|string',
'config_files' => 'sometimes|nullable|json',
'config_startup' => 'sometimes|nullable|json',
'config_stop' => 'sometimes|nullable|string|max:191',
// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id',
'startup' => 'sometimes|string',
'script_container' => 'sometimes|string',
'script_entry' => 'sometimes|string',
'script_install' => 'sometimes|string',
];
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs\Variables;
use Pterodactyl\Models\EggVariable;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreEggVariableRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return [
'name' => 'required|string|min:1|max:191',
'description' => 'sometimes|string|nullable',
'env_variable' => 'required|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES,
'default_value' => 'present',
'user_viewable' => 'required|boolean',
'user_editable' => 'required|boolean',
'rules' => 'bail|required|string',
];
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs\Variables;
use Pterodactyl\Models\EggVariable;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class UpdateEggVariablesRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return [
'*' => 'array',
'*.id' => 'required|integer',
'*.name' => 'sometimes|string|min:1|max:191',
'*.description' => 'sometimes|string|nullable',
'*.env_variable' => 'sometimes|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES,
'*.default_value' => 'sometimes|present',
'*.user_viewable' => 'sometimes|boolean',
'*.user_editable' => 'sometimes|boolean',
'*.rules' => 'sometimes|string',
];
}
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Locations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteLocationRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS;
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Locations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetLocationsRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS;
protected int $permission = AdminAcl::READ;
}

View file

@ -3,18 +3,10 @@
namespace Pterodactyl\Http\Requests\Api\Application\Locations;
use Pterodactyl\Models\Location;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreLocationRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS;
protected int $permission = AdminAcl::WRITE;
/**
* Rules to validate the request against.
*/
public function rules(): array
{
return collect(Location::getRules())->only([
@ -23,9 +15,6 @@ class StoreLocationRequest extends ApplicationApiRequest
])->toArray();
}
/**
* Rename fields to be more clear in error messages.
*/
public function attributes(): array
{
return [

View file

@ -6,14 +6,9 @@ use Pterodactyl\Models\Location;
class UpdateLocationRequest extends StoreLocationRequest
{
/**
* Rules to validate this request against.
*/
public function rules(): array
{
/** @var Location $location */
$location = $this->route()->parameter('location');
$locationId = $location->id;
$locationId = $this->route()->parameter('location')->id;
return collect(Location::getRulesForUpdate($locationId))->only([
'short',

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteMountRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
class GetMountRequest extends GetMountsRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetMountsRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class MountEggsRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? ['eggs' => 'required|exists:eggs,id'];
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class MountNodesRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? ['nodes' => 'required|exists:nodes,id'];
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Models\Mount;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreMountRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? Mount::getRules();
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Models\Mount;
class UpdateMountRequest extends StoreMountRequest
{
public function rules(array $rules = null): array
{
return $rules ?? Mount::getRulesForUpdate($this->route()->parameter('mount')->id);
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteNestRequest extends ApplicationApiRequest
{
}

View file

@ -1,13 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetEggRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_EGGS;
protected int $permission = AdminAcl::READ;
}

View file

@ -1,13 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetEggsRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_EGGS;
protected int $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
class GetNestRequest extends GetNestsRequest
{
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetNestsRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_NESTS;
protected int $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,14 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Models\Nest;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreNestRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? Nest::getRules();
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Models\Nest;
class UpdateNestRequest extends StoreNestRequest
{
public function rules(array $rules = null): array
{
return $rules ?? Nest::getRulesForUpdate($this->route()->parameter('nest')->id);
}
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteNodeRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_NODES;
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetNodesRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_NODES;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,48 +2,50 @@
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Illuminate\Support\Arr;
use Pterodactyl\Models\Node;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreNodeRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_NODES;
protected int $permission = AdminAcl::WRITE;
/**
* Validation rules to apply to this request.
*/
public function rules(array $rules = null): array
{
return collect($rules ?? Node::getRules())->only([
'public',
'name',
'description',
'location_id',
'database_host_id',
'fqdn',
'scheme',
'behind_proxy',
'maintenance_mode',
'public',
'listen_port_http',
'public_port_http',
'listen_port_sftp',
'public_port_sftp',
'memory',
'memory_overallocate',
'disk',
'disk_overallocate',
'upload_size',
'daemonListen',
'daemonSFTP',
'daemonBase',
])->mapWithKeys(function ($value, $key) {
$key = ($key === 'daemonSFTP') ? 'daemonSftp' : $key;
'upload_size',
'daemon_base',
])->mapWithKeys(function ($value, $key) {
return [snake_case($key) => $value];
})->toArray();
}
/**
* Fields to rename for clarity in the API response.
*
* @return array
*/
public function attributes(): array
public function attributes()
{
return [
'daemon_base' => 'Daemon Base Path',
@ -56,15 +58,20 @@ class StoreNodeRequest extends ApplicationApiRequest
/**
* Change the formatting of some data keys in the validated response data
* to match what the application expects in the services.
*
* @param string|null $key
* @param string|array|null $default
*
* @return mixed
*/
public function validated($key = null, $default = null): array
public function validated($key = null, $default = null)
{
$response = parent::validated();
$response['daemonListen'] = $response['daemon_listen'];
$response['daemonSFTP'] = $response['daemon_sftp'];
$response['daemonBase'] = $response['daemon_base'] ?? (new Node())->getAttribute('daemonBase');
$response['daemon_base'] = $response['daemon_base'] ?? Node::DEFAULT_DAEMON_BASE;
unset($response['daemon_base'], $response['daemon_listen'], $response['daemon_sftp']);
if (!is_null($key)) {
return Arr::get($response, $key, $default);
}
return $response;
}

View file

@ -6,15 +6,8 @@ use Pterodactyl\Models\Node;
class UpdateNodeRequest extends StoreNodeRequest
{
/**
* Apply validation rules to this request. Uses the parent class rules()
* function but passes in the rules for updating rather than creating.
*/
public function rules(array $rules = null): array
{
/** @var Node $node */
$node = $this->route()->parameter('node');
return parent::rules(Node::getRulesForUpdate($node->id));
return parent::rules($rules ?? Node::getRulesForUpdate($this->route()->parameter('node')->id));
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteRoleRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
class GetRoleRequest extends GetRolesRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetRolesRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,14 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
use Pterodactyl\Models\AdminRole;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreRoleRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? AdminRole::getRules();
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
use Pterodactyl\Models\AdminRole;
class UpdateRoleRequest extends StoreRoleRequest
{
public function rules(array $rules = null): array
{
return $rules ?? AdminRole::getRulesForUpdate($this->route()->parameter('role')->id);
}
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetServerDatabaseRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetServerDatabasesRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,9 +2,6 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
class ServerDatabaseWriteRequest extends GetServerDatabasesRequest
{
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,26 +2,18 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Illuminate\Support\Arr;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Server;
use Illuminate\Validation\Rule;
use Illuminate\Database\Query\Builder;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Services\Databases\DatabaseManagementService;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreServerDatabaseRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
protected int $permission = AdminAcl::WRITE;
/**
* Validation rules for database creation.
*/
public function rules(): array
{
/** @var Server $server */
$server = $this->route()->parameter('server');
return [
@ -40,20 +32,22 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest
}
/**
* Return data formatted in the correct format for the service to consume.
* @param string|null $key
* @param string|array|null $default
*
* @return mixed
*/
public function validated($key = null, $default = null): array
public function validated($key = null, $default = null)
{
return [
$data = [
'database' => $this->input('database'),
'remote' => $this->input('remote'),
'database_host_id' => $this->input('host'),
];
return is_null($key) ? $data : Arr::get($data, $key, $default);
}
/**
* Format error messages in a more understandable format for API output.
*/
public function attributes(): array
{
return [
@ -63,12 +57,8 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest
];
}
/**
* Returns the database name in the expected format.
*/
public function databaseName(): string
{
/** @var Server $server */
$server = $this->route()->parameter('server');
Assert::isInstanceOf($server, Server::class);

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetExternalServerRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVERS;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetServerRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVERS;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class ServerWriteRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVERS;
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,22 +2,12 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Illuminate\Support\Arr;
use Pterodactyl\Models\Server;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Validator;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Models\Objects\DeploymentObject;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreServerRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVERS;
protected int $permission = AdminAcl::WRITE;
/**
* Rules to be applied to this request.
*/
public function rules(): array
{
$rules = Server::getRules();
@ -26,15 +16,9 @@ class StoreServerRequest extends ApplicationApiRequest
'external_id' => $rules['external_id'],
'name' => $rules['name'],
'description' => array_merge(['nullable'], $rules['description']),
'user' => $rules['owner_id'],
'egg' => $rules['egg_id'],
'docker_image' => $rules['image'],
'startup' => $rules['startup'],
'environment' => 'present|array',
'skip_scripts' => 'sometimes|boolean',
'oom_disabled' => 'sometimes|boolean',
'owner_id' => $rules['owner_id'],
'node_id' => $rules['node_id'],
// Resource limitations
'limits' => 'required|array',
'limits.memory' => $rules['memory'],
'limits.swap' => $rules['swap'],
@ -42,110 +26,64 @@ class StoreServerRequest extends ApplicationApiRequest
'limits.io' => $rules['io'],
'limits.threads' => $rules['threads'],
'limits.cpu' => $rules['cpu'],
'limits.oom_killer' => 'required|boolean',
// Application Resource Limits
'feature_limits' => 'required|array',
'feature_limits.databases' => $rules['database_limit'],
'feature_limits.allocations' => $rules['allocation_limit'],
'feature_limits.backups' => $rules['backup_limit'],
'feature_limits.databases' => $rules['database_limit'],
// Placeholders for rules added in withValidator() function.
'allocation.default' => '',
'allocation.additional.*' => '',
'allocation.default' => 'required|bail|integer|exists:allocations,id',
'allocation.additional.*' => 'integer|exists:allocations,id',
// Automatic deployment rules
'deploy' => 'sometimes|required|array',
'deploy.locations' => 'array',
'deploy.locations.*' => 'integer|min:1',
'deploy.dedicated_ip' => 'required_with:deploy,boolean',
'deploy.port_range' => 'array',
'deploy.port_range.*' => 'string',
'start_on_completion' => 'sometimes|boolean',
'startup' => $rules['startup'],
'environment' => 'present|array',
'egg_id' => $rules['egg_id'],
'image' => $rules['image'],
'skip_scripts' => 'present|boolean',
];
}
/**
* Normalize the data into a format that can be consumed by the service.
* @param string|null $key
* @param string|array|null $default
*
* @return array
*/
public function validated($key = null, $default = null): array
public function validated($key = null, $default = null)
{
$data = parent::validated();
return [
$response = [
'external_id' => array_get($data, 'external_id'),
'name' => array_get($data, 'name'),
'description' => array_get($data, 'description'),
'owner_id' => array_get($data, 'user'),
'egg_id' => array_get($data, 'egg'),
'image' => array_get($data, 'docker_image'),
'startup' => array_get($data, 'startup'),
'environment' => array_get($data, 'environment'),
'owner_id' => array_get($data, 'owner_id'),
'node_id' => array_get($data, 'node_id'),
'memory' => array_get($data, 'limits.memory'),
'swap' => array_get($data, 'limits.swap'),
'disk' => array_get($data, 'limits.disk'),
'io' => array_get($data, 'limits.io'),
'cpu' => array_get($data, 'limits.cpu'),
'threads' => array_get($data, 'limits.threads'),
'skip_scripts' => array_get($data, 'skip_scripts', false),
'allocation_id' => array_get($data, 'allocation.default'),
'allocation_additional' => array_get($data, 'allocation.additional'),
'start_on_completion' => array_get($data, 'start_on_completion', false),
'database_limit' => array_get($data, 'feature_limits.databases'),
'cpu' => array_get($data, 'limits.cpu'),
'oom_disabled' => !array_get($data, 'limits.oom_killer'),
'allocation_limit' => array_get($data, 'feature_limits.allocations'),
'backup_limit' => array_get($data, 'feature_limits.backups'),
'oom_disabled' => array_get($data, 'oom_disabled'),
'database_limit' => array_get($data, 'feature_limits.databases'),
'allocation_id' => array_get($data, 'allocation.default'),
'allocation_additional' => array_get($data, 'allocation.additional'),
'startup' => array_get($data, 'startup'),
'environment' => array_get($data, 'environment'),
'egg_id' => array_get($data, 'egg_id'),
'image' => array_get($data, 'image'),
'skip_scripts' => array_get($data, 'skip_scripts'),
'start_on_completion' => array_get($data, 'start_on_completion', false),
];
}
/*
* Run validation after the rules above have been applied.
*
* @param \Illuminate\Validation\Validator $validator
*/
public function withValidator(Validator $validator): void
{
$validator->sometimes('allocation.default', [
'required', 'integer', 'bail',
Rule::exists('allocations', 'id')->where(function ($query) {
$query->whereNull('server_id');
}),
], function ($input) {
return !$input->deploy;
});
$validator->sometimes('allocation.additional.*', [
'integer',
Rule::exists('allocations', 'id')->where(function ($query) {
$query->whereNull('server_id');
}),
], function ($input) {
return !$input->deploy;
});
$validator->sometimes('deploy.locations', 'present', function ($input) {
return $input->deploy;
});
$validator->sometimes('deploy.port_range', 'present', function ($input) {
return $input->deploy;
});
}
/**
* Return a deployment object that can be passed to the server creation service.
*/
public function getDeploymentObject(): ?DeploymentObject
{
if (is_null($this->input('deploy'))) {
return null;
}
$object = new DeploymentObject();
$object->setDedicated($this->input('deploy.dedicated_ip', false));
$object->setLocations($this->input('deploy.locations', []));
$object->setPorts($this->input('deploy.port_range', []));
return $object;
return is_null($key) ? $response : Arr::get($response, $key, $default);
}
}

View file

@ -2,6 +2,7 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Illuminate\Support\Arr;
use Pterodactyl\Models\Server;
use Illuminate\Support\Collection;
@ -12,7 +13,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest
*/
public function rules(): array
{
$rules = Server::getRulesForUpdate($this->parameter('server', Server::class));
$rules = Server::getRulesForUpdate($this->route()->parameter('server')->id);
return [
'allocation' => $rules['allocation_id'],
@ -26,7 +27,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest
'limits.threads' => $this->requiredToOptional('threads', $rules['threads'], true),
'limits.disk' => $this->requiredToOptional('disk', $rules['disk'], true),
// Legacy rules to maintain backwards compatable API support without requiring
// Legacy rules to maintain backwards compatible API support without requiring
// a major version bump.
//
// @see https://github.com/pterodactyl/panel/issues/1500
@ -51,8 +52,13 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest
/**
* Convert the allocation field into the expected format for the service handler.
*
* @param string|null $key
* @param string|array|null $default
*
* @return mixed
*/
public function validated($key = null, $default = null): array
public function validated($key = null, $default = null)
{
$data = parent::validated();
@ -71,13 +77,19 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest
unset($data['limits']);
}
if (!is_null($key)) {
return Arr::get($data, $key, $default);
}
return $data;
}
/**
* Custom attributes to use in error message responses.
*
* @return array
*/
public function attributes(): array
public function attributes()
{
return [
'add_allocations' => 'allocations to add',
@ -95,9 +107,11 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest
* compatability with the old API endpoint while also supporting a more correct API
* call.
*
* @return array
*
* @see https://github.com/pterodactyl/panel/issues/1500
*/
protected function requiredToOptional(string $field, array $rules, bool $limits = false): array
protected function requiredToOptional(string $field, array $rules, bool $limits = false)
{
if (!in_array('required', $rules)) {
return $rules;

View file

@ -2,6 +2,7 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Illuminate\Support\Arr;
use Pterodactyl\Models\Server;
class UpdateServerDetailsRequest extends ServerWriteRequest
@ -11,7 +12,7 @@ class UpdateServerDetailsRequest extends ServerWriteRequest
*/
public function rules(): array
{
$rules = Server::getRulesForUpdate($this->parameter('server', Server::class));
$rules = Server::getRulesForUpdate($this->route()->parameter('server')->id);
return [
'external_id' => $rules['external_id'],
@ -24,19 +25,24 @@ class UpdateServerDetailsRequest extends ServerWriteRequest
/**
* Convert the posted data into the correct format that is expected
* by the application.
*
* @param string|null $key
* @param string|array|null $default
*/
public function validated($key = null, $default = null): array
public function validated($key = null, $default = null)
{
return [
$data = [
'external_id' => $this->input('external_id'),
'name' => $this->input('name'),
'owner_id' => $this->input('user'),
'description' => $this->input('description'),
];
return is_null($key) ? $data : Arr::get($data, $key, $default);
}
/**
* Rename some attributes in error messages to clarify the field
* Rename some of the attributes in error messages to clarify the field
* being discussed.
*/
public function attributes(): array

View file

@ -0,0 +1,77 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Illuminate\Support\Arr;
use Pterodactyl\Models\Server;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class UpdateServerRequest extends ApplicationApiRequest
{
public function rules(): array
{
$rules = Server::getRules();
return [
'external_id' => $rules['external_id'],
'name' => $rules['name'],
'description' => array_merge(['nullable'], $rules['description']),
'owner_id' => $rules['owner_id'],
'limits' => 'sometimes|array',
'limits.memory' => $rules['memory'],
'limits.swap' => $rules['swap'],
'limits.disk' => $rules['disk'],
'limits.io' => $rules['io'],
'limits.threads' => $rules['threads'],
'limits.cpu' => $rules['cpu'],
'limits.oom_killer' => 'sometimes|boolean',
'feature_limits' => 'required|array',
'feature_limits.allocations' => $rules['allocation_limit'],
'feature_limits.backups' => $rules['backup_limit'],
'feature_limits.databases' => $rules['database_limit'],
'allocation_id' => 'bail|exists:allocations,id',
'add_allocations' => 'bail|array',
'add_allocations.*' => 'integer',
'remove_allocations' => 'bail|array',
'remove_allocations.*' => 'integer',
];
}
/**
* @param string|null $key
* @param string|array|null $default
*
* @return mixed
*/
public function validated($key = null, $default = null)
{
$data = parent::validated();
$response = [
'external_id' => array_get($data, 'external_id'),
'name' => array_get($data, 'name'),
'description' => array_get($data, 'description'),
'owner_id' => array_get($data, 'owner_id'),
'memory' => array_get($data, 'limits.memory'),
'swap' => array_get($data, 'limits.swap'),
'disk' => array_get($data, 'limits.disk'),
'io' => array_get($data, 'limits.io'),
'threads' => array_get($data, 'limits.threads'),
'cpu' => array_get($data, 'limits.cpu'),
'oom_disabled' => !array_get($data, 'limits.oom_killer'),
'allocation_limit' => array_get($data, 'feature_limits.allocations'),
'backup_limit' => array_get($data, 'feature_limits.backups'),
'database_limit' => array_get($data, 'feature_limits.databases'),
'allocation_id' => array_get($data, 'allocation_id'),
'add_allocations' => array_get($data, 'add_allocations'),
'remove_allocations' => array_get($data, 'remove_allocations'),
];
return is_null($key) ? $response : Arr::get($response, $key, $default);
}
}

View file

@ -3,41 +3,20 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Models\Server;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class UpdateServerStartupRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVERS;
protected int $permission = AdminAcl::WRITE;
/**
* Validation rules to run the input against.
*/
public function rules(): array
{
$data = Server::getRulesForUpdate($this->parameter('server', Server::class));
$rules = Server::getRulesForUpdate($this->route()->parameter('server')->id);
return [
'startup' => $data['startup'],
'startup' => $rules['startup'],
'environment' => 'present|array',
'egg' => $data['egg_id'],
'image' => $data['image'],
'egg_id' => $rules['egg_id'],
'image' => $rules['image'],
'skip_scripts' => 'present|boolean',
];
}
/**
* Return the validated data in a format that is expected by the service.
*/
public function validated($key = null, $default = null): array
{
$data = parent::validated();
return collect($data)->only(['startup', 'environment', 'skip_scripts'])->merge([
'egg_id' => array_get($data, 'egg'),
'docker_image' => array_get($data, 'image'),
])->toArray();
}
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Users;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteUserRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_USERS;
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Users;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetExternalUserRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_USERS;
protected int $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Users;
class GetUserRequest extends GetUsersRequest
{
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Users;
use Pterodactyl\Services\Acl\Api\AdminAcl as Acl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetUsersRequest extends ApplicationApiRequest
{
protected ?string $resource = Acl::RESOURCE_USERS;
protected int $permission = Acl::READ;
}

View file

@ -3,59 +3,21 @@
namespace Pterodactyl\Http\Requests\Api\Application\Users;
use Pterodactyl\Models\User;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreUserRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_USERS;
protected int $permission = AdminAcl::WRITE;
/**
* Return the validation rules for this request.
*/
public function rules(array $rules = null): array
{
$rules = $rules ?? User::getRules();
$response = collect($rules)->only([
return collect($rules)->only([
'external_id',
'email',
'username',
'password',
'language',
'admin_role_id',
'root_admin',
])->toArray();
$response['first_name'] = $rules['name_first'];
$response['last_name'] = $rules['name_last'];
return $response;
}
public function validated($key = null, $default = null): array
{
$data = parent::validated();
$data['name_first'] = $data['first_name'];
$data['name_last'] = $data['last_name'];
unset($data['first_name'], $data['last_name']);
return $data;
}
/**
* Rename some fields to be more user friendly.
*/
public function attributes(): array
{
return [
'external_id' => 'Third Party Identifier',
'name_first' => 'First Name',
'name_last' => 'Last Name',
'root_admin' => 'Root Administrator Status',
];
}
}

View file

@ -6,13 +6,8 @@ use Pterodactyl\Models\User;
class UpdateUserRequest extends StoreUserRequest
{
/**
* Return the validation rules for this request.
*/
public function rules(array $rules = null): array
{
$userId = $this->parameter('user', User::class)->id;
return parent::rules(User::getRulesForUpdate($userId));
return parent::rules($rules ?? User::getRulesForUpdate($this->route()->parameter('user')->id));
}
}