diff --git a/.gitignore b/.gitignore index c3db9d64b..47346505f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ yarn-error.log # PHP /vendor -.php_cs.cache +.php-cs-fixer.cache .phpunit.result.cache _ide_helper.php _ide_helper_models.php @@ -80,3 +80,6 @@ _ide_helper_models.php !.env.example .env* *.log +_ide_helper_models.php +_ide_helper.php +.phpstorm.meta.php diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 97d0c648f..64f13be9b 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -21,7 +21,7 @@ return (new Config()) 'no_unreachable_default_argument_value' => true, 'no_useless_return' => true, 'ordered_imports' => [ - 'sortAlgorithm' => 'length', + 'sort_algorithm' => 'length', ], 'phpdoc_align' => [ 'align' => 'left', diff --git a/app/Contracts/Http/ClientPermissionsRequest.php b/app/Contracts/Http/ClientPermissionsRequest.php deleted file mode 100644 index 85eae2259..000000000 --- a/app/Contracts/Http/ClientPermissionsRequest.php +++ /dev/null @@ -1,13 +0,0 @@ - Response::HTTP_UNAUTHORIZED, + ValidationException::class => Response::HTTP_UNPROCESSABLE_ENTITY, + ]; + /** * Registers the exception handling callbacks for the application. This * will capture specific exception types that we do not want to include @@ -191,7 +202,7 @@ class Handler extends ExceptionHandler 'code' => class_basename($exception), 'status' => method_exists($exception, 'getStatusCode') ? strval($exception->getStatusCode()) - : ($exception instanceof ValidationException ? '422' : '500'), + : strval(self::$statusCodeMap[get_class($exception)] ?? 500), 'detail' => $exception instanceof HttpExceptionInterface ? $exception->getMessage() : 'An unexpected error was encountered while processing this request, please try again.', @@ -212,6 +223,7 @@ class Handler extends ExceptionHandler 'file' => str_replace(Application::getInstance()->basePath(), '', $exception->getFile()), ], 'meta' => [ + 'class' => get_class($exception), 'trace' => explode("\n", $exception->getTraceAsString()), ], ]); diff --git a/app/Exceptions/Transformer/InvalidTransformerLevelException.php b/app/Exceptions/Transformer/InvalidTransformerLevelException.php deleted file mode 100644 index 3d4c24248..000000000 --- a/app/Exceptions/Transformer/InvalidTransformerLevelException.php +++ /dev/null @@ -1,9 +0,0 @@ -accessToken = $accessToken; + $this->plainTextToken = $plainTextToken; + } +} diff --git a/app/Extensions/Spatie/Fractalistic/Fractal.php b/app/Extensions/Spatie/Fractalistic/Fractal.php index b9c651d35..adb3c96a9 100644 --- a/app/Extensions/Spatie/Fractalistic/Fractal.php +++ b/app/Extensions/Spatie/Fractalistic/Fractal.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Extensions\Spatie\Fractalistic; -use League\Fractal\TransformerAbstract; use Spatie\Fractal\Fractal as SpatieFractal; +use Pterodactyl\Transformers\Api\Transformer; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use Pterodactyl\Extensions\League\Fractal\Serializers\PterodactylSerializer; @@ -33,12 +33,9 @@ class Fractal extends SpatieFractal // If the resource name is not set attempt to pull it off the transformer // itself and set it automatically. - if ( - is_null($this->resourceName) - && $this->transformer instanceof TransformerAbstract - && method_exists($this->transformer, 'getResourceName') - ) { - $this->resourceName = $this->transformer->getResourceName(); + $class = is_string($this->transformer) ? new $this->transformer() : $this->transformer; + if (is_null($this->resourceName) && $class instanceof Transformer) { + $this->resourceName = $class->getResourceName(); } return parent::createData(); diff --git a/app/Http/Controllers/Admin/BaseController.php b/app/Http/Controllers/Admin/BaseController.php index 317228f08..0452fa148 100644 --- a/app/Http/Controllers/Admin/BaseController.php +++ b/app/Http/Controllers/Admin/BaseController.php @@ -9,8 +9,6 @@ class BaseController extends Controller { /** * Return the admin index view. - * - * @return \Illuminate\View\View */ public function index(): View { diff --git a/app/Http/Controllers/Api/Application/ApplicationApiController.php b/app/Http/Controllers/Api/Application/ApplicationApiController.php index f931388eb..2a696c02a 100644 --- a/app/Http/Controllers/Api/Application/ApplicationApiController.php +++ b/app/Http/Controllers/Api/Application/ApplicationApiController.php @@ -3,17 +3,16 @@ namespace Pterodactyl\Http\Controllers\Api\Application; use Illuminate\Http\Request; -use Webmozart\Assert\Assert; use Illuminate\Http\Response; use Illuminate\Support\Collection; use Illuminate\Container\Container; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Extensions\Spatie\Fractalistic\Fractal; -use Pterodactyl\Transformers\Api\Application\BaseTransformer; abstract class ApplicationApiController extends Controller { protected Request $request; + protected Fractal $fractal; /** @@ -45,25 +44,6 @@ abstract class ApplicationApiController extends Controller $this->request = $request; } - /** - * Return an instance of an application transformer. - * - * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - */ - public function getTransformer(string $abstract) - { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = Container::getInstance()->make($abstract); - $transformer->setRootAdmin($this->request->user()->root_admin); - $transformer->setKey($this->request->attributes->get('api_key')); - - Assert::isInstanceOf($transformer, BaseTransformer::class); - - return $transformer; - } - /** * Return a HTTP/201 response for the API. */ diff --git a/app/Http/Controllers/Api/Application/Databases/DatabaseController.php b/app/Http/Controllers/Api/Application/Databases/DatabaseController.php index a743f6d6d..3398572a3 100644 --- a/app/Http/Controllers/Api/Application/Databases/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Databases/DatabaseController.php @@ -51,7 +51,7 @@ class DatabaseController extends ApplicationApiController ->paginate($perPage); return $this->fractal->collection($databases) - ->transformWith($this->getTransformer(DatabaseHostTransformer::class)) + ->transformWith(DatabaseHostTransformer::class) ->toArray(); } @@ -63,7 +63,7 @@ class DatabaseController extends ApplicationApiController public function view(GetDatabaseRequest $request, DatabaseHost $databaseHost): array { return $this->fractal->item($databaseHost) - ->transformWith($this->getTransformer(DatabaseHostTransformer::class)) + ->transformWith(DatabaseHostTransformer::class) ->toArray(); } @@ -77,7 +77,7 @@ class DatabaseController extends ApplicationApiController $databaseHost = $this->creationService->handle($request->validated()); return $this->fractal->item($databaseHost) - ->transformWith($this->getTransformer(DatabaseHostTransformer::class)) + ->transformWith(DatabaseHostTransformer::class) ->respond(JsonResponse::HTTP_CREATED); } @@ -91,7 +91,7 @@ class DatabaseController extends ApplicationApiController $databaseHost = $this->updateService->handle($databaseHost->id, $request->validated()); return $this->fractal->item($databaseHost) - ->transformWith($this->getTransformer(DatabaseHostTransformer::class)) + ->transformWith(DatabaseHostTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Eggs/EggController.php b/app/Http/Controllers/Api/Application/Eggs/EggController.php index 25d3a05a7..5d5c8572d 100644 --- a/app/Http/Controllers/Api/Application/Eggs/EggController.php +++ b/app/Http/Controllers/Api/Application/Eggs/EggController.php @@ -52,7 +52,7 @@ class EggController extends ApplicationApiController } return $this->fractal->collection($eggs) - ->transformWith($this->getTransformer(EggTransformer::class)) + ->transformWith(EggTransformer::class) ->toArray(); } @@ -64,7 +64,7 @@ class EggController extends ApplicationApiController public function view(GetEggRequest $request, Egg $egg): array { return $this->fractal->item($egg) - ->transformWith($this->getTransformer(EggTransformer::class)) + ->transformWith(EggTransformer::class) ->toArray(); } @@ -78,7 +78,7 @@ class EggController extends ApplicationApiController $egg = Egg::query()->create($request->validated()); return $this->fractal->item($egg) - ->transformWith($this->getTransformer(EggTransformer::class)) + ->transformWith(EggTransformer::class) ->respond(JsonResponse::HTTP_CREATED); } @@ -92,7 +92,7 @@ class EggController extends ApplicationApiController $egg->update($request->validated()); return $this->fractal->item($egg) - ->transformWith($this->getTransformer(EggTransformer::class)) + ->transformWith(EggTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index de121c2e8..f737bcc47 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -57,7 +57,7 @@ class LocationController extends ApplicationApiController ->paginate($perPage); return $this->fractal->collection($locations) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->toArray(); } @@ -69,7 +69,7 @@ class LocationController extends ApplicationApiController public function view(GetLocationRequest $request, Location $location): array { return $this->fractal->item($location) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->toArray(); } @@ -85,7 +85,7 @@ class LocationController extends ApplicationApiController $location = $this->creationService->handle($request->validated()); return $this->fractal->item($location) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->addMeta([ 'resource' => route('api.application.locations.view', [ 'location' => $location->id, @@ -106,7 +106,7 @@ class LocationController extends ApplicationApiController $location = $this->updateService->handle($location, $request->validated()); return $this->fractal->item($location) - ->transformWith($this->getTransformer(LocationTransformer::class)) + ->transformWith(LocationTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Mounts/MountController.php b/app/Http/Controllers/Api/Application/Mounts/MountController.php index 9070f4155..9a5ddfc1b 100644 --- a/app/Http/Controllers/Api/Application/Mounts/MountController.php +++ b/app/Http/Controllers/Api/Application/Mounts/MountController.php @@ -2,8 +2,8 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Mounts; -use Pterodactyl\Models\Mount; use Illuminate\Http\Response; +use Pterodactyl\Models\Mount; use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Transformers\Api\Application\MountTransformer; @@ -45,7 +45,7 @@ class MountController extends ApplicationApiController ->paginate($perPage); return $this->fractal->collection($mounts) - ->transformWith($this->getTransformer(MountTransformer::class)) + ->transformWith(MountTransformer::class) ->toArray(); } @@ -57,7 +57,7 @@ class MountController extends ApplicationApiController public function view(GetMountRequest $request, Mount $mount): array { return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + ->transformWith(MountTransformer::class) ->toArray(); } @@ -71,7 +71,7 @@ class MountController extends ApplicationApiController $mount = Mount::query()->create($request->validated()); return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + ->transformWith(MountTransformer::class) ->respond(JsonResponse::HTTP_CREATED); } @@ -85,7 +85,7 @@ class MountController extends ApplicationApiController $mount->update($request->validated()); return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + ->transformWith(MountTransformer::class) ->toArray(); } @@ -116,7 +116,7 @@ class MountController extends ApplicationApiController } return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + ->transformWith(MountTransformer::class) ->toArray(); } @@ -135,7 +135,7 @@ class MountController extends ApplicationApiController } return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + ->transformWith(MountTransformer::class) ->toArray(); } @@ -154,7 +154,7 @@ class MountController extends ApplicationApiController } return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + ->transformWith(MountTransformer::class) ->toArray(); } @@ -173,7 +173,7 @@ class MountController extends ApplicationApiController } return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + ->transformWith(MountTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Nests/NestController.php b/app/Http/Controllers/Api/Application/Nests/NestController.php index 008f4f1db..9872140c0 100644 --- a/app/Http/Controllers/Api/Application/Nests/NestController.php +++ b/app/Http/Controllers/Api/Application/Nests/NestController.php @@ -63,7 +63,7 @@ class NestController extends ApplicationApiController } return $this->fractal->collection($nests) - ->transformWith($this->getTransformer(NestTransformer::class)) + ->transformWith(NestTransformer::class) ->toArray(); } @@ -75,7 +75,7 @@ class NestController extends ApplicationApiController public function view(GetNestRequest $request, Nest $nest): array { return $this->fractal->item($nest) - ->transformWith($this->getTransformer(NestTransformer::class)) + ->transformWith(NestTransformer::class) ->toArray(); } @@ -90,7 +90,7 @@ class NestController extends ApplicationApiController $nest = $this->nestCreationService->handle($request->validated()); return $this->fractal->item($nest) - ->transformWith($this->getTransformer(NestTransformer::class)) + ->transformWith(NestTransformer::class) ->toArray(); } @@ -106,7 +106,7 @@ class NestController extends ApplicationApiController $this->nestUpdateService->handle($nest->id, $request->validated()); return $this->fractal->item($nest) - ->transformWith($this->getTransformer(NestTransformer::class)) + ->transformWith(NestTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php index d993e278b..ffa4c2fe8 100644 --- a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -47,7 +47,7 @@ class AllocationController extends ApplicationApiController $allocations = $node->allocations()->paginate($perPage); return $this->fractal->collection($allocations) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index fdf86ccc6..ef06336f7 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -61,7 +61,7 @@ class NodeController extends ApplicationApiController ->paginate($perPage); return $this->fractal->collection($nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } @@ -73,7 +73,7 @@ class NodeController extends ApplicationApiController public function view(GetNodeRequest $request, Node $node): array { return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } @@ -89,7 +89,7 @@ class NodeController extends ApplicationApiController $node = $this->creationService->handle($request->validated()); return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->addMeta([ 'resource' => route('api.application.nodes.view', [ 'node' => $node->id, @@ -112,7 +112,7 @@ class NodeController extends ApplicationApiController ); return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php index bb004b5b3..cebf5a8a0 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php @@ -38,7 +38,7 @@ class NodeDeploymentController extends ApplicationApiController ->handle($request->query('per_page'), $request->query('page')); return $this->fractal->collection($nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) + ->transformWith(NodeTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Roles/RoleController.php b/app/Http/Controllers/Api/Application/Roles/RoleController.php index e4cff2821..aab08f276 100644 --- a/app/Http/Controllers/Api/Application/Roles/RoleController.php +++ b/app/Http/Controllers/Api/Application/Roles/RoleController.php @@ -6,8 +6,8 @@ use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\AdminRole; use Spatie\QueryBuilder\QueryBuilder; -use Pterodactyl\Transformers\Api\Application\AdminRoleTransformer; use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; +use Pterodactyl\Transformers\Api\Application\AdminRoleTransformer; use Pterodactyl\Http\Requests\Api\Application\Roles\GetRoleRequest; use Pterodactyl\Http\Requests\Api\Application\Roles\GetRolesRequest; use Pterodactyl\Http\Requests\Api\Application\Roles\StoreRoleRequest; @@ -43,7 +43,7 @@ class RoleController extends ApplicationApiController ->paginate($perPage); return $this->fractal->collection($roles) - ->transformWith($this->getTransformer(AdminRoleTransformer::class)) + ->transformWith(AdminRoleTransformer::class) ->toArray(); } @@ -55,7 +55,7 @@ class RoleController extends ApplicationApiController public function view(GetRoleRequest $request, AdminRole $role): array { return $this->fractal->item($role) - ->transformWith($this->getTransformer(AdminRoleTransformer::class)) + ->transformWith(AdminRoleTransformer::class) ->toArray(); } @@ -72,7 +72,7 @@ class RoleController extends ApplicationApiController $role = AdminRole::query()->create($data); return $this->fractal->item($role) - ->transformWith($this->getTransformer(AdminRoleTransformer::class)) + ->transformWith(AdminRoleTransformer::class) ->respond(JsonResponse::HTTP_CREATED); } @@ -86,7 +86,7 @@ class RoleController extends ApplicationApiController $role->update($request->validated()); return $this->fractal->item($role) - ->transformWith($this->getTransformer(AdminRoleTransformer::class)) + ->transformWith(AdminRoleTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index 3f3b3efb7..5089e1e0c 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -46,7 +46,7 @@ class DatabaseController extends ApplicationApiController public function index(GetServerDatabasesRequest $request, Server $server): array { return $this->fractal->collection($server->databases) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->transformWith(ServerDatabaseTransformer::class) ->toArray(); } @@ -58,7 +58,7 @@ class DatabaseController extends ApplicationApiController public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array { return $this->fractal->item($database) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->transformWith(ServerDatabaseTransformer::class) ->toArray(); } @@ -86,7 +86,7 @@ class DatabaseController extends ApplicationApiController ])); return $this->fractal->item($database) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + ->transformWith(ServerDatabaseTransformer::class) ->addMeta([ 'resource' => route('api.application.servers.databases.view', [ 'server' => $server->id, diff --git a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php index 43feb3e9e..8e35e027e 100644 --- a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Servers; +use Pterodactyl\Models\Server; use Pterodactyl\Transformers\Api\Application\ServerTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Servers\GetExternalServerRequest; @@ -13,10 +14,12 @@ class ExternalServerController extends ApplicationApiController * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ - public function index(GetExternalServerRequest $request): array + public function index(GetExternalServerRequest $request, string $external_id): array { - return $this->fractal->item($request->getServerModel()) - ->transformWith($this->getTransformer(ServerTransformer::class)) + $server = Server::query()->where('external_id', $external_id)->firstOrFail(); + + return $this->fractal->item($server) + ->transformWith(ServerTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index da537c9e8..9d650c025 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -52,7 +52,7 @@ class ServerController extends ApplicationApiController ->paginate($perPage); return $this->fractal->collection($servers) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } @@ -71,7 +71,7 @@ class ServerController extends ApplicationApiController $server = $this->creationService->handle($request->validated(), $request->getDeploymentObject()); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->respond(Response::HTTP_CREATED); } @@ -83,7 +83,7 @@ class ServerController extends ApplicationApiController public function view(GetServerRequest $request, Server $server): array { return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php index 857d46f22..f4d403dbf 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -41,7 +41,7 @@ class ServerDetailsController extends ApplicationApiController ); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } @@ -55,7 +55,7 @@ class ServerDetailsController extends ApplicationApiController $server = $this->buildModificationService->handle($server, $request->validated()); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/StartupController.php b/app/Http/Controllers/Api/Application/Servers/StartupController.php index 1513be2fe..123dcdeb1 100644 --- a/app/Http/Controllers/Api/Application/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Application/Servers/StartupController.php @@ -35,7 +35,7 @@ class StartupController extends ApplicationApiController ->handle($server, $request->validated()); return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php index ab1083d26..9129578f0 100644 --- a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php +++ b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Users; +use Pterodactyl\Models\User; use Pterodactyl\Transformers\Api\Application\UserTransformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Requests\Api\Application\Users\GetExternalUserRequest; @@ -13,10 +14,12 @@ class ExternalUserController extends ApplicationApiController * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ - public function index(GetExternalUserRequest $request): array + public function index(GetExternalUserRequest $request, string $external_id): array { - return $this->fractal->item($request->getUserModel()) - ->transformWith($this->getTransformer(UserTransformer::class)) + $user = User::query()->where('external_id', $external_id)->firstOrFail(); + + return $this->fractal->item($user) + ->transformWith(UserTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index 5fe2ca75a..592192183 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -63,7 +63,7 @@ class UserController extends ApplicationApiController ->paginate($perPage); return $this->fractal->collection($users) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->toArray(); } @@ -76,7 +76,7 @@ class UserController extends ApplicationApiController public function view(GetUserRequest $request, User $user): array { return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->toArray(); } @@ -98,7 +98,7 @@ class UserController extends ApplicationApiController $user = $this->updateService->handle($user, $request->validated()); return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->toArray(); } @@ -114,7 +114,7 @@ class UserController extends ApplicationApiController $user = $this->creationService->handle($request->validated()); return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + ->transformWith(UserTransformer::class) ->addMeta([ 'resource' => route('api.application.users.view', [ 'user' => $user->id, diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php index 85a109007..cbceb07c8 100644 --- a/app/Http/Controllers/Api/Client/AccountController.php +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -34,7 +34,7 @@ class AccountController extends ClientApiController public function index(Request $request): array { return $this->fractal->item($request->user()) - ->transformWith($this->getTransformer(AccountTransformer::class)) + ->transformWith(AccountTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index 1473d9f51..dc4fc59c7 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -3,46 +3,22 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Illuminate\Http\Response; -use Pterodactyl\Models\ApiKey; use Pterodactyl\Exceptions\DisplayException; -use Illuminate\Contracts\Encryption\Encrypter; -use Pterodactyl\Services\Api\KeyCreationService; -use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; -use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -use Pterodactyl\Transformers\Api\Client\ApiKeyTransformer; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Pterodactyl\Http\Requests\Api\Client\AccountApiRequest; use Pterodactyl\Http\Requests\Api\Client\Account\StoreApiKeyRequest; +use Pterodactyl\Transformers\Api\Client\PersonalAccessTokenTransformer; class ApiKeyController extends ClientApiController { - private Encrypter $encrypter; - private ApiKeyRepository $repository; - private KeyCreationService $keyCreationService; - - /** - * ApiKeyController constructor. - */ - public function __construct( - Encrypter $encrypter, - ApiKeyRepository $repository, - KeyCreationService $keyCreationService - ) { - parent::__construct(); - - $this->encrypter = $encrypter; - $this->repository = $repository; - $this->keyCreationService = $keyCreationService; - } - /** * Returns all of the API keys that exist for the given client. * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ - public function index(ClientApiRequest $request): array + public function index(AccountApiRequest $request): array { - return $this->fractal->collection($request->user()->apiKeys) - ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + return $this->fractal->collection($request->user()->tokens) + ->transformWith(PersonalAccessTokenTransformer::class) ->toArray(); } @@ -50,25 +26,22 @@ class ApiKeyController extends ClientApiController * Store a new API key for a user's account. * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function store(StoreApiKeyRequest $request): array { - if ($request->user()->apiKeys->count() >= 5) { + if ($request->user()->tokens->count() >= 10) { throw new DisplayException('You have reached the account limit for number of API keys.'); } - $key = $this->keyCreationService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([ - 'user_id' => $request->user()->id, - 'memo' => $request->input('description'), - 'allowed_ips' => $request->input('allowed_ips') ?? [], - ]); + // TODO: this should accept an array of different scopes to apply as permissions + // for the token. Right now it allows any account level permission. + $token = $request->user()->createToken($request->input('description')); - return $this->fractal->item($key) - ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + return $this->fractal->item($token->accessToken) + ->transformWith(PersonalAccessTokenTransformer::class) ->addMeta([ - 'secret_token' => $this->encrypter->decrypt($key->token), + 'secret_token' => $token->plainTextToken, ]) ->toArray(); } @@ -76,17 +49,9 @@ class ApiKeyController extends ClientApiController /** * Deletes a given API key. */ - public function delete(ClientApiRequest $request, string $identifier): Response + public function delete(AccountApiRequest $request, string $id): Response { - $response = $this->repository->deleteWhere([ - 'key_type' => ApiKey::TYPE_ACCOUNT, - 'user_id' => $request->user()->id, - 'identifier' => $identifier, - ]); - - if (!$response) { - throw new NotFoundHttpException(); - } + $request->user()->tokens()->where('token_id', $id)->delete(); return $this->returnNoContent(); } diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index 851e0b583..05dbb8894 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -2,10 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client; -use Webmozart\Assert\Assert; -use Illuminate\Container\Container; -use Pterodactyl\Transformers\Daemon\BaseDaemonTransformer; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; abstract class ClientApiController extends ApplicationApiController @@ -15,7 +12,7 @@ abstract class ClientApiController extends ApplicationApiController * * @return string[] */ - protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []): array + protected function getIncludesForTransformer(Transformer $transformer, array $merge = []): array { $filtered = array_filter($this->parseIncludes(), function ($datum) use ($transformer) { return in_array($datum, $transformer->getAvailableIncludes()); @@ -41,28 +38,4 @@ abstract class ClientApiController extends ApplicationApiController return trim($item); }, explode(',', $includes)); } - - /** - * Return an instance of an application transformer. - * - * @return \Pterodactyl\Transformers\Api\Client\BaseClientTransformer - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - */ - public function getTransformer(string $abstract) - { - /** @var \Pterodactyl\Transformers\Api\Client\BaseClientTransformer $transformer */ - $transformer = Container::getInstance()->make($abstract); - Assert::isInstanceOfAny($transformer, [ - BaseClientTransformer::class, - BaseDaemonTransformer::class, - ]); - - if ($transformer instanceof BaseClientTransformer) { - $transformer->setKey($this->request->attributes->get('api_key')); - $transformer->setUser($this->request->user()); - } - - return $transformer; - } } diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index 804a1d215..67b15555d 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -34,11 +34,10 @@ class ClientController extends ClientApiController public function index(GetServersRequest $request): array { $user = $request->user(); - $transformer = $this->getTransformer(ServerTransformer::class); // Start the query builder and ensure we eager load any requested relationships from the request. $builder = QueryBuilder::for( - Server::query()->with($this->getIncludesForTransformer($transformer, ['node'])) + Server::query()->with($this->getIncludesForTransformer(new ServerTransformer(), ['node'])) )->allowedFilters([ 'uuid', 'name', @@ -69,7 +68,7 @@ class ClientController extends ClientApiController $servers = $builder->paginate(min($request->query('per_page', 50), 100))->appends($request->query()); - return $this->fractal->transformWith($transformer)->collection($servers)->toArray(); + return $this->fractal->transformWith(new ServerTransformer())->collection($servers)->toArray(); } /** diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php index 848712f96..d8e3cea32 100644 --- a/app/Http/Controllers/Api/Client/SSHKeyController.php +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -20,13 +20,12 @@ class SSHKeyController extends ClientApiController public function index(Request $request): \Pterodactyl\Extensions\Spatie\Fractalistic\Fractal { return $this->fractal->collection(UserSSHKey::query()->where('user_id', '=', $request->user()->id)->get()) - ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)); + ->transformWith(UserSSHKeyTransformer::class); } /** * ? * - * @return JsonResponse * @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws \Pterodactyl\Exceptions\DisplayException */ @@ -42,7 +41,7 @@ class SSHKeyController extends ClientApiController $key = UserSSHKey::query()->create($data); return $this->fractal->item($key) - ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) + ->transformWith(UserSSHKeyTransformer::class) ->respond(JsonResponse::HTTP_CREATED); } diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php index a12772f0d..0bbe0d396 100644 --- a/app/Http/Controllers/Api/Client/Servers/BackupController.php +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -12,8 +12,8 @@ use Pterodactyl\Models\Permission; use Illuminate\Auth\Access\AuthorizationException; use Pterodactyl\Services\Backups\DeleteBackupService; use Pterodactyl\Services\Backups\DownloadLinkService; -use Pterodactyl\Services\Backups\InitiateBackupService; use Pterodactyl\Repositories\Eloquent\BackupRepository; +use Pterodactyl\Services\Backups\InitiateBackupService; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Transformers\Api\Client\BackupTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -63,7 +63,7 @@ class BackupController extends ClientApiController $limit = min($request->query('per_page') ?? 20, 50); return $this->fractal->collection($server->backups()->paginate($limit)) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->addMeta([ 'backup_count' => $this->repository->getNonFailedBackups($server)->count(), ]) @@ -100,7 +100,7 @@ class BackupController extends ClientApiController }); return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->toArray(); } @@ -126,7 +126,7 @@ class BackupController extends ClientApiController $backup->refresh(); return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->toArray(); } @@ -142,7 +142,7 @@ class BackupController extends ClientApiController } return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + ->transformWith(BackupTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index c3a5cb0d9..2b5258fbd 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -48,7 +48,7 @@ class DatabaseController extends ClientApiController public function index(GetDatabasesRequest $request, Server $server): array { return $this->fractal->collection($server->databases) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseTransformer::class) ->toArray(); } @@ -65,7 +65,7 @@ class DatabaseController extends ClientApiController return $this->fractal->item($database) ->parseIncludes(['password']) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseTransformer::class) ->toArray(); } @@ -82,7 +82,7 @@ class DatabaseController extends ClientApiController return $this->fractal->item($database) ->parseIncludes(['password']) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index a425a82f5..caf8e7ee8 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -57,7 +57,7 @@ class FileController extends ClientApiController ->getDirectory($request->get('directory') ?? '/'); return $this->fractal->collection($contents) - ->transformWith($this->getTransformer(FileObjectTransformer::class)) + ->transformWith(FileObjectTransformer::class) ->toArray(); } @@ -202,7 +202,7 @@ class FileController extends ClientApiController }); return $this->fractal->item($file) - ->transformWith($this->getTransformer(FileObjectTransformer::class)) + ->transformWith(FileObjectTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index aabbe15d3..606cabefe 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -47,7 +47,7 @@ class NetworkAllocationController extends ClientApiController public function index(GetNetworkRequest $request, Server $server): array { return $this->fractal->collection($server->allocations) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -65,7 +65,7 @@ class NetworkAllocationController extends ClientApiController ]); return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -81,7 +81,7 @@ class NetworkAllocationController extends ClientApiController $this->serverRepository->update($server->id, ['allocation_id' => $allocation->id]); return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } @@ -100,7 +100,7 @@ class NetworkAllocationController extends ClientApiController $allocation = $this->assignableAllocationService->handle($server); return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->transformWith(AllocationTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php index 5031690eb..5bf8317eb 100644 --- a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php +++ b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php @@ -42,7 +42,7 @@ class ResourceUtilizationController extends ClientApiController }); return $this->fractal->item($stats) - ->transformWith($this->getTransformer(StatsTransformer::class)) + ->transformWith(StatsTransformer::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index 9d5e229e2..ade67ca5d 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -14,7 +14,6 @@ use Pterodactyl\Repositories\Eloquent\ScheduleRepository; use Pterodactyl\Services\Schedules\ProcessScheduleService; use Pterodactyl\Transformers\Api\Client\ScheduleTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\ViewScheduleRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreScheduleRequest; @@ -49,7 +48,7 @@ class ScheduleController extends ClientApiController $schedules->loadMissing('tasks'); return $this->fractal->collection($schedules) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } @@ -77,7 +76,7 @@ class ScheduleController extends ClientApiController ]); return $this->fractal->item($model) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } @@ -88,14 +87,10 @@ class ScheduleController extends ClientApiController */ public function view(ViewScheduleRequest $request, Server $server, Schedule $schedule): array { - if ($schedule->server_id !== $server->id) { - throw new NotFoundHttpException(); - } - $schedule->loadMissing('tasks'); return $this->fractal->item($schedule) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } @@ -134,7 +129,7 @@ class ScheduleController extends ClientApiController $this->repository->update($schedule->id, $data); return $this->fractal->item($schedule->refresh()) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + ->transformWith(ScheduleTransformer::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index f8379e5e5..6448aa581 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -6,15 +6,15 @@ use Pterodactyl\Models\Task; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; -use Pterodactyl\Models\Permission; use Pterodactyl\Repositories\Eloquent\TaskRepository; use Pterodactyl\Exceptions\Http\HttpForbiddenException; use Pterodactyl\Transformers\Api\Client\TaskTransformer; -use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Exceptions\Service\ServiceLimitExceededException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreTaskRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\DeleteScheduleRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\UpdateScheduleRequest; class ScheduleTaskController extends ClientApiController { @@ -63,7 +63,7 @@ class ScheduleTaskController extends ClientApiController ]); return $this->fractal->item($task) - ->transformWith($this->getTransformer(TaskTransformer::class)) + ->transformWith(TaskTransformer::class) ->toArray(); } @@ -93,7 +93,7 @@ class ScheduleTaskController extends ClientApiController ]); return $this->fractal->item($task->refresh()) - ->transformWith($this->getTransformer(TaskTransformer::class)) + ->transformWith(TaskTransformer::class) ->toArray(); } @@ -101,18 +101,14 @@ class ScheduleTaskController extends ClientApiController * Delete a given task for a schedule. If there are subsequent tasks stored in the database * for this schedule their sequence IDs are decremented properly. * + * This uses the UpdateScheduleRequest intentionally -- there is no permission specific + * to deleting a given task on a schedule, so we'll assume if you have permission to edit + * a schedule that you can then remove a task from said schedule. + * * @throws \Exception */ - public function delete(ClientApiRequest $request, Server $server, Schedule $schedule, Task $task): Response + public function delete(DeleteScheduleRequest $request, Server $server, Schedule $schedule, Task $task): Response { - if ($task->schedule_id !== $schedule->id || $schedule->server_id !== $server->id) { - throw new NotFoundHttpException(); - } - - if (!$request->user()->can(Permission::ACTION_SCHEDULE_UPDATE, $server)) { - throw new HttpForbiddenException('You do not have permission to perform this action.'); - } - $schedule->tasks()->where('sequence_id', '>', $task->sequence_id)->update([ 'sequence_id' => $schedule->tasks()->getConnection()->raw('(sequence_id - 1)'), ]); diff --git a/app/Http/Controllers/Api/Client/Servers/ServerController.php b/app/Http/Controllers/Api/Client/Servers/ServerController.php index 1702ee71c..7c1e17e03 100644 --- a/app/Http/Controllers/Api/Client/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Client/Servers/ServerController.php @@ -34,7 +34,7 @@ class ServerController extends ClientApiController public function index(GetServerRequest $request, Server $server): array { return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + ->transformWith(ServerTransformer::class) ->addMeta([ 'is_server_owner' => $request->user()->id === $server->owner_id, 'user_permissions' => $this->permissionsService->handle($server, $request->user()), diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index 6f9c634b4..856bae29d 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -42,7 +42,7 @@ class StartupController extends ClientApiController return $this->fractal->collection( $server->variables()->where('user_viewable', true)->get() ) - ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->transformWith(EggVariableTransformer::class) ->addMeta([ 'startup_command' => $startup, 'docker_images' => $server->egg->docker_images, @@ -86,7 +86,7 @@ class StartupController extends ClientApiController $startup = $this->startupCommandService->handle($server, false); return $this->fractal->item($variable) - ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->transformWith(EggVariableTransformer::class) ->addMeta([ 'startup_command' => $startup, 'raw_startup_command' => $server->startup, diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index d20efd2ba..807a10984 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Illuminate\Http\Request; use Illuminate\Http\Response; use Pterodactyl\Models\Server; +use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Permission; use Illuminate\Support\Facades\Log; use Pterodactyl\Repositories\Eloquent\SubuserRepository; @@ -47,7 +48,7 @@ class SubuserController extends ClientApiController public function index(GetSubuserRequest $request, Server $server): array { return $this->fractal->collection($server->subusers) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } @@ -56,12 +57,10 @@ class SubuserController extends ClientApiController * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ - public function view(GetSubuserRequest $request): array + public function view(GetSubuserRequest $request, Server $server, Subuser $subuser): array { - $subuser = $request->attributes->get('subuser'); - return $this->fractal->item($subuser) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } @@ -82,7 +81,7 @@ class SubuserController extends ClientApiController ); return $this->fractal->item($response) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } @@ -93,11 +92,8 @@ class SubuserController extends ClientApiController * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Illuminate\Contracts\Container\BindingResolutionException */ - public function update(UpdateSubuserRequest $request, Server $server): array + public function update(UpdateSubuserRequest $request, Server $server, Subuser $subuser): array { - /** @var \Pterodactyl\Models\Subuser $subuser */ - $subuser = $request->attributes->get('subuser'); - $permissions = $this->getDefaultPermissions($request); $current = $subuser->permissions; @@ -121,18 +117,15 @@ class SubuserController extends ClientApiController } return $this->fractal->item($subuser->refresh()) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + ->transformWith(SubuserTransformer::class) ->toArray(); } /** * Removes a subusers from a server's assignment. */ - public function delete(DeleteSubuserRequest $request, Server $server): Response + public function delete(DeleteSubuserRequest $request, Server $server, Subuser $subuser): Response { - /** @var \Pterodactyl\Models\Subuser $subuser */ - $subuser = $request->attributes->get('subuser'); - $this->repository->delete($subuser->id); try { diff --git a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php index 4dc1f4026..d13600872 100644 --- a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php +++ b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php @@ -5,11 +5,10 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Carbon\CarbonImmutable; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; -use Pterodactyl\Models\Permission; use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Exceptions\Http\HttpForbiddenException; -use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Services\Servers\GetUserPermissionsService; +use Pterodactyl\Http\Requests\Api\Client\WebsocketTokenRequest; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; class WebsocketController extends ClientApiController @@ -36,14 +35,9 @@ class WebsocketController extends ClientApiController * allows us to continually renew this token and avoid users maintaining sessions wrongly, * as well as ensure that user's only perform actions they're allowed to. */ - public function __invoke(ClientApiRequest $request, Server $server): JsonResponse + public function __invoke(WebsocketTokenRequest $request, Server $server): JsonResponse { - $user = $request->user(); - if ($user->cannot(Permission::ACTION_WEBSOCKET_CONNECT, $server)) { - throw new HttpForbiddenException('You do not have permission to connect to this server\'s websocket.'); - } - - $permissions = $this->permissionsService->handle($server, $user); + $permissions = $this->permissionsService->handle($server, $request->user()); $node = $server->node; if (!is_null($server->transfer)) { @@ -65,7 +59,7 @@ class WebsocketController extends ClientApiController 'server_uuid' => $server->uuid, 'permissions' => $permissions, ]) - ->handle($node, $user->id . $server->uuid); + ->handle($node, $request->user()->id . $server->uuid); $socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $node->getConnectionAddress()); diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index 396f889be..3abc2522b 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -55,8 +55,6 @@ class TwoFactorController extends ClientApiController /** * Updates a user's account to have two-factor enabled. * - * @return \Illuminate\Http\JsonResponse - * * @throws \Throwable * @throws \Illuminate\Validation\ValidationException * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException diff --git a/app/Http/Controllers/Api/Client/WebauthnController.php b/app/Http/Controllers/Api/Client/WebauthnController.php index c88d8eba3..3e3aad679 100644 --- a/app/Http/Controllers/Api/Client/WebauthnController.php +++ b/app/Http/Controllers/Api/Client/WebauthnController.php @@ -5,8 +5,8 @@ namespace Pterodactyl\Http\Controllers\Api\Client; use Exception; use Illuminate\Http\Request; use Illuminate\Http\JsonResponse; -use LaravelWebauthn\Facades\Webauthn; use Pterodactyl\Models\WebauthnKey; +use LaravelWebauthn\Facades\Webauthn; use Webauthn\PublicKeyCredentialCreationOptions; use Illuminate\Database\Eloquent\ModelNotFoundException; use Pterodactyl\Transformers\Api\Client\WebauthnKeyTransformer; @@ -24,7 +24,7 @@ class WebauthnController extends ClientApiController public function index(Request $request): array { return $this->fractal->collection(WebauthnKey::query()->where('user_id', '=', $request->user()->id)->get()) - ->transformWith($this->getTransformer(WebauthnKeyTransformer::class)) + ->transformWith(WebauthnKeyTransformer::class) ->toArray(); } @@ -88,7 +88,7 @@ class WebauthnController extends ClientApiController ); return $this->fractal->item($webauthnKey) - ->transformWith($this->getTransformer(WebauthnKeyTransformer::class)) + ->transformWith(WebauthnKeyTransformer::class) ->toArray(); } catch (Exception $e) { return new JsonResponse([ diff --git a/app/Http/Controllers/Auth/LoginCheckpointController.php b/app/Http/Controllers/Auth/LoginCheckpointController.php index dffa79998..d38f31230 100644 --- a/app/Http/Controllers/Auth/LoginCheckpointController.php +++ b/app/Http/Controllers/Auth/LoginCheckpointController.php @@ -17,7 +17,6 @@ class LoginCheckpointController extends AbstractLoginController private Encrypter $encrypter; private Google2FA $google2FA; - /** * LoginCheckpointController constructor. */ @@ -51,6 +50,7 @@ class LoginCheckpointController extends AbstractLoginController { if ($this->hasTooManyLoginAttempts($request)) { $this->sendLockoutResponse($request); + return; } @@ -66,6 +66,7 @@ class LoginCheckpointController extends AbstractLoginController null, 'The authentication token provided has expired, please refresh the page and try again.' ); + return; } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 0e676bbb7..bf12ceb82 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -69,6 +69,7 @@ class LoginController extends AbstractLoginController if ($this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); $this->sendLockoutResponse($request); + return; } @@ -77,6 +78,7 @@ class LoginController extends AbstractLoginController $user = $this->repository->findFirstWhere([[$useColumn, '=', $username]]); } catch (RecordNotFoundException $exception) { $this->sendFailedLoginResponse($request); + return; } @@ -86,12 +88,13 @@ class LoginController extends AbstractLoginController // can proceed to the next step in the login process. if (!password_verify($request->input('password'), $user->password)) { $this->sendFailedLoginResponse($request, $user); + return; } $webauthnKeys = $user->webauthnKeys()->get(); - if (sizeof($webauthnKeys) > 0) { + if (count($webauthnKeys) > 0) { $token = Str::random(64); $this->cache->put($token, $user->id, CarbonImmutable::now()->addMinutes(5)); @@ -99,7 +102,7 @@ class LoginController extends AbstractLoginController $request->session()->put(self::SESSION_PUBLICKEY_REQUEST, $publicKey); $request->session()->save(); - $methods = [ self::METHOD_WEBAUTHN ]; + $methods = [self::METHOD_WEBAUTHN]; if ($user->use_totp) { $methods[] = self::METHOD_TOTP; } @@ -112,19 +115,17 @@ class LoginController extends AbstractLoginController 'public_key' => $publicKey, ], ]); - } else if ($user->use_totp) { + } elseif ($user->use_totp) { $token = Str::random(64); $this->cache->put($token, $user->id, CarbonImmutable::now()->addMinutes(5)); return new JsonResponse([ 'complete' => false, - 'methods' => [ self::METHOD_TOTP ], + 'methods' => [self::METHOD_TOTP], 'confirmation_token' => $token, ]); } - $this->auth->guard()->login($user, true); - return $this->sendLoginResponse($user, $request); } } diff --git a/app/Http/Controllers/Auth/WebauthnController.php b/app/Http/Controllers/Auth/WebauthnController.php index 438c3b342..7a85b9e19 100644 --- a/app/Http/Controllers/Auth/WebauthnController.php +++ b/app/Http/Controllers/Auth/WebauthnController.php @@ -3,8 +3,8 @@ namespace Pterodactyl\Http\Controllers\Auth; use Exception; -use Pterodactyl\Models\User; use Illuminate\Http\Request; +use Pterodactyl\Models\User; use Illuminate\Auth\AuthManager; use Illuminate\Http\JsonResponse; use LaravelWebauthn\Facades\Webauthn; @@ -36,6 +36,7 @@ class WebauthnController extends AbstractLoginController { if ($this->hasTooManyLoginAttempts($request)) { $this->sendLockoutResponse($request); + return; } @@ -51,6 +52,7 @@ class WebauthnController extends AbstractLoginController null, 'The authentication token provided has expired, please refresh the page and try again.' ); + return; } $this->auth->guard()->onceUsingId($user->id); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d2e0f2cc7..fd3cf0b95 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http; -use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; use Illuminate\Auth\Middleware\Authenticate; use Pterodactyl\Http\Middleware\TrimStrings; @@ -16,24 +15,23 @@ use Pterodactyl\Http\Middleware\AdminAuthenticate; use Illuminate\Routing\Middleware\ThrottleRequests; use Pterodactyl\Http\Middleware\LanguageMiddleware; use Illuminate\Foundation\Http\Kernel as HttpKernel; -use Pterodactyl\Http\Middleware\Api\AuthenticateKey; use Illuminate\Routing\Middleware\SubstituteBindings; -use Pterodactyl\Http\Middleware\Api\SetSessionDriver; use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\View\Middleware\ShareErrorsFromSession; use Pterodactyl\Http\Middleware\MaintenanceMiddleware; use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; -use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess; -use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; +use Pterodactyl\Http\Middleware\Api\PreventUnboundModels; use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; -use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; +use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings; +use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; +use Pterodactyl\Http\Middleware\Api\Application\SubstituteApplicationApiBindings; class Kernel extends HttpKernel { @@ -43,12 +41,11 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - CheckForMaintenanceMode::class, - EncryptCookies::class, + TrustProxies::class, + PreventRequestsDuringMaintenance::class, ValidatePostSize::class, TrimStrings::class, ConvertEmptyStringsToNull::class, - TrustProxies::class, ]; /** @@ -58,6 +55,7 @@ class Kernel extends HttpKernel */ protected $middlewareGroups = [ 'web' => [ + EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, AuthenticateSession::class, @@ -69,20 +67,19 @@ class Kernel extends HttpKernel ], 'api' => [ IsValidJson::class, - ApiSubstituteBindings::class, - SetSessionDriver::class, - 'api..key:' . ApiKey::TYPE_APPLICATION, + EnsureFrontendRequestsAreStateful::class, + 'auth:sanctum', + SubstituteApplicationApiBindings::class, + PreventUnboundModels::class, AuthenticateApplicationUser::class, - AuthenticateIPAccess::class, + RequireTwoFactorAuthentication::class, ], 'client-api' => [ - StartSession::class, - SetSessionDriver::class, - AuthenticateSession::class, IsValidJson::class, + EnsureFrontendRequestsAreStateful::class, + 'auth:sanctum', SubstituteClientApiBindings::class, - 'api..key:' . ApiKey::TYPE_ACCOUNT, - AuthenticateIPAccess::class, + PreventUnboundModels::class, // This is perhaps a little backwards with the Client API, but logically you'd be unable // to create/get an API key without first enabling 2FA on the account, so I suppose in the // end it makes sense. @@ -112,8 +109,5 @@ class Kernel extends HttpKernel 'bindings' => SubstituteBindings::class, 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, - - // API Specific Middleware - 'api..key' => AuthenticateKey::class, ]; } diff --git a/app/Http/Middleware/Api/ApiSubstituteBindings.php b/app/Http/Middleware/Api/ApiSubstituteBindings.php deleted file mode 100644 index 7ade7452a..000000000 --- a/app/Http/Middleware/Api/ApiSubstituteBindings.php +++ /dev/null @@ -1,87 +0,0 @@ - Allocation::class, - 'database' => Database::class, - 'egg' => Egg::class, - 'location' => Location::class, - 'nest' => Nest::class, - 'node' => Node::class, - 'server' => Server::class, - 'user' => User::class, - ]; - - /** - * @var \Illuminate\Routing\Router - */ - protected $router; - - /** - * Perform substitution of route parameters without triggering - * a 404 error if a model is not found. - * - * @param \Illuminate\Http\Request $request - * - * @return mixed - */ - public function handle($request, Closure $next) - { - $route = $request->route(); - - foreach (self::$mappings as $key => $model) { - if (!is_null($this->router->getBindingCallback($key))) { - continue; - } - - $this->router->model($key, $model, function () use ($request) { - $request->attributes->set('is_missing_model', true); - }); - } - - $this->router->substituteBindings($route); - - // Attempt to resolve bindings for this route. If one of the models - // cannot be resolved do not immediately return a 404 error. Set a request - // attribute that can be checked in the base API request class to only - // trigger a 404 after validating that the API key making the request is valid - // and even has permission to access the requested resource. - try { - $this->router->substituteImplicitBindings($route); - } catch (ModelNotFoundException $exception) { - $request->attributes->set('is_missing_model', true); - } - - return $next($request); - } - - /** - * Return the registered mappings. - * - * @return array - */ - public static function getMappings() - { - return self::$mappings; - } -} diff --git a/app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php b/app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php new file mode 100644 index 000000000..4ae63ff4a --- /dev/null +++ b/app/Http/Middleware/Api/Application/SubstituteApplicationApiBindings.php @@ -0,0 +1,66 @@ + Allocation::class, + 'database' => Database::class, + 'egg' => Egg::class, + 'location' => Location::class, + 'nest' => Nest::class, + 'node' => Node::class, + 'server' => Server::class, + 'user' => User::class, + ]; + + public function __construct(Registrar $router) + { + $this->router = $router; + } + + /** + * Perform substitution of route parameters without triggering + * a 404 error if a model is not found. + * + * @param \Illuminate\Http\Request $request + * + * @return mixed + */ + public function handle($request, Closure $next) + { + foreach (self::$mappings as $key => $class) { + $this->router->bind($key, $class); + } + + try { + $this->router->substituteImplicitBindings($route = $request->route()); + } catch (ModelNotFoundException $exception) { + if (isset($route) && $route->getMissing()) { + $route->getMissing()($request); + } + + throw $exception; + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/AuthenticateIPAccess.php b/app/Http/Middleware/Api/AuthenticateIPAccess.php deleted file mode 100644 index 2af34cfd9..000000000 --- a/app/Http/Middleware/Api/AuthenticateIPAccess.php +++ /dev/null @@ -1,38 +0,0 @@ -attributes->get('api_key'); - - if (is_null($model->allowed_ips) || empty($model->allowed_ips)) { - return $next($request); - } - - $find = new IP($request->ip()); - foreach ($model->allowed_ips as $ip) { - if (Range::parse($ip)->contains($find)) { - return $next($request); - } - } - - throw new AccessDeniedHttpException('This IP address (' . $request->ip() . ') does not have permission to access the API using these credentials.'); - } -} diff --git a/app/Http/Middleware/Api/AuthenticateKey.php b/app/Http/Middleware/Api/AuthenticateKey.php deleted file mode 100644 index 1a9ff2e5e..000000000 --- a/app/Http/Middleware/Api/AuthenticateKey.php +++ /dev/null @@ -1,95 +0,0 @@ -auth = $auth; - $this->encrypter = $encrypter; - $this->repository = $repository; - } - - /** - * Handle an API request by verifying that the provided API key - * is in a valid format and exists in the database. - * - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Request $request, Closure $next, int $keyType) - { - if (is_null($request->bearerToken()) && is_null($request->user())) { - throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']); - } - - $raw = $request->bearerToken(); - - // This is a request coming through using cookies, we have an authenticated user not using - // an API key. Make some fake API key models and continue on through the process. - if (empty($raw) && $request->user() instanceof User) { - $model = (new ApiKey())->forceFill([ - 'user_id' => $request->user()->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); - } else { - $model = $this->authenticateApiKey($raw, $keyType); - $this->auth->guard()->loginUsingId($model->user_id); - } - - $request->attributes->set('api_key', $model); - - return $next($request); - } - - /** - * Authenticate an API key. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - protected function authenticateApiKey(string $key, int $keyType): ApiKey - { - $identifier = substr($key, 0, ApiKey::IDENTIFIER_LENGTH); - $token = substr($key, ApiKey::IDENTIFIER_LENGTH); - - try { - $model = $this->repository->findFirstWhere([ - ['identifier', '=', $identifier], - ['key_type', '=', $keyType], - ]); - } catch (RecordNotFoundException $exception) { - throw new AccessDeniedHttpException(); - } - - if (!hash_equals($this->encrypter->decrypt($model->token), $token)) { - throw new AccessDeniedHttpException(); - } - - $this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => CarbonImmutable::now()]); - - return $model; - } -} diff --git a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php index 31590f28a..3a1a80f49 100644 --- a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php +++ b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php @@ -3,60 +3,102 @@ namespace Pterodactyl\Http\Middleware\Api\Client; use Closure; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Backup; -use Pterodactyl\Models\Database; +use Illuminate\Support\Str; +use Pterodactyl\Models\Task; +use Illuminate\Routing\Route; +use Pterodactyl\Models\Server; use Illuminate\Container\Container; +use Illuminate\Database\Query\JoinClause; +use Illuminate\Contracts\Routing\Registrar; use Pterodactyl\Contracts\Extensions\HashidsInterface; -use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Illuminate\Database\Eloquent\ModelNotFoundException; -class SubstituteClientApiBindings extends ApiSubstituteBindings +class SubstituteClientApiBindings { + protected Registrar $router; + + public function __construct(Registrar $router) + { + $this->router = $router; + } + /** - * Perform substitution of route parameters without triggering - * a 404 error if a model is not found. + * Perform substitution of route parameters for the Client API. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request * * @return mixed */ public function handle($request, Closure $next) { - // Override default behavior of the model binding to use a specific table - // column rather than the default 'id'. - $this->router->bind('server', function ($value) use ($request) { - try { - $column = 'uuidShort'; - if (preg_match('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $value)) { - $column = 'uuid'; - } - - return Container::getInstance()->make(ServerRepositoryInterface::class)->findFirstWhere([ - [$column, '=', $value], - ]); - } catch (RecordNotFoundException $ex) { - $request->attributes->set('is_missing_model', true); - - return null; - } + $this->router->bind('server', function ($value) { + return Server::query()->where(Str::length($value) === 8 ? 'uuidShort' : 'uuid', $value)->firstOrFail(); }); - $this->router->bind('database', function ($value) { + $this->router->bind('allocation', function ($value, $route) { + return $this->server($route)->allocations()->findOrFail($value); + }); + + $this->router->bind('schedule', function ($value, $route) { + return $this->server($route)->schedule()->findOrFail($value); + }); + + $this->router->bind('task', function ($value, $route) { + return Task::query() + ->select('tasks.*') + ->join('schedules', function (JoinClause $join) use ($route) { + $join->on('schedules.id', 'tasks.schedule_id') + ->where('schedules.server_id', $route->parameter('server')->id); + }) + ->where('schedules.id', $route->parameter('schedule')->id) + ->findOrFail($value); + }); + + $this->router->bind('database', function ($value, $route) { $id = Container::getInstance()->make(HashidsInterface::class)->decodeFirst($value); - return Database::query()->where('id', $id)->firstOrFail(); + return $this->server($route)->databases()->findOrFail($id); }); - $this->router->bind('backup', function ($value) { - return Backup::query()->where('uuid', $value)->firstOrFail(); + $this->router->bind('backup', function ($value, $route) { + return $this->server($route)->backups()->where('uuid', $value)->firstOrFail(); }); - $this->router->bind('user', function ($value) { - return User::query()->where('uuid', $value)->firstOrFail(); + $this->router->bind('subuser', function ($value, $route) { + return $this->server($route)->subusers() + ->select('subusers.*') + ->join('users', 'subusers.user_id', '=', 'users.id') + ->where('users.uuid', $value) + ->firstOrFail(); }); - return parent::handle($request, $next); + try { + /* @var \Illuminate\Routing\Route $route */ + $this->router->substituteBindings($route = $request->route()); + } catch (ModelNotFoundException $exception) { + if (isset($route) && $route->getMissing()) { + $route->getMissing()($request); + } + + throw $exception; + } + + return $next($request); + } + + /** + * Plucks the server model off the route. If no server model is present a + * ModelNotFound exception will be thrown. + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + private function server(Route $route): Server + { + $server = $route->parameter('server'); + if (!$server instanceof Server) { + throw (new ModelNotFoundException())->setModel(Server::class, [$route->parameter('server')]); + } + + return $server; } } diff --git a/app/Http/Middleware/Api/PreventUnboundModels.php b/app/Http/Middleware/Api/PreventUnboundModels.php new file mode 100644 index 000000000..1b1a959a7 --- /dev/null +++ b/app/Http/Middleware/Api/PreventUnboundModels.php @@ -0,0 +1,45 @@ +route(); + $parameters = $route->parameters() ?? []; + + /** @var \ReflectionParameter[] $signatures */ + $signatures = $route->signatureParameters(UrlRoutable::class); + foreach ($signatures as $signature) { + $class = Reflector::getParameterClassName($signature); + if (is_null($class) || !is_subclass_of($class, Model::class)) { + continue; + } + + if (!$parameters[$signature->getName()] instanceof Model) { + throw new Exception(sprintf('No parameter binding has been defined for model [%s] using route parameter key "%s".', $class, $signature->getName())); + } + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/Api/SetSessionDriver.php b/app/Http/Middleware/Api/SetSessionDriver.php deleted file mode 100644 index 1c8f59a0b..000000000 --- a/app/Http/Middleware/Api/SetSessionDriver.php +++ /dev/null @@ -1,35 +0,0 @@ -config = $config; - } - - /** - * Set the session for API calls to only last for the one request. - * - * @return mixed - */ - public function handle(Request $request, Closure $next) - { - $this->config->set('session.driver', 'array'); - - return $next($request); - } -} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 08d960353..9408cfe43 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -14,6 +14,6 @@ class VerifyCsrfToken extends BaseVerifier protected $except = [ 'remote/*', 'daemon/*', - 'api/*', + // 'api/*', ]; } diff --git a/app/Http/Requests/Api/ApiRequest.php b/app/Http/Requests/Api/ApiRequest.php new file mode 100644 index 000000000..253a61691 --- /dev/null +++ b/app/Http/Requests/Api/ApiRequest.php @@ -0,0 +1,83 @@ +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; + } +} diff --git a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php index 1729d7209..d062f7648 100644 --- a/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/DeleteAllocationRequest.php @@ -2,31 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Allocation; -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; - - /** - * Determine if the requested allocation exists and belongs to the node that - * is being passed in the URL. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - $allocation = $this->route()->parameter('allocation'); - - if ($node instanceof Node && $node->exists) { - if ($allocation instanceof Allocation && $allocation->exists && $allocation->node_id === $node->id) { - return true; - } - } - - return false; - } } diff --git a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php index b811fe526..2f536af60 100644 --- a/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/GetAllocationsRequest.php @@ -2,23 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Models\Node; -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; - - /** - * Determine if the node that we are requesting the allocations - * for exists on the Panel. - */ - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - - return $node instanceof Node && $node->exists; - } } diff --git a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php index 5bd79970d..dd97e1332 100644 --- a/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php +++ b/app/Http/Requests/Api/Application/Allocations/StoreAllocationRequest.php @@ -2,14 +2,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Allocations; -use Pterodactyl\Services\Acl\Api\AdminAcl; 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 [ diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index 143549dbf..70f6cb219 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -2,147 +2,16 @@ namespace Pterodactyl\Http\Requests\Api\Application; -use Pterodactyl\Models\ApiKey; -use Pterodactyl\Services\Acl\Api\AdminAcl; -use Illuminate\Foundation\Http\FormRequest; -use Pterodactyl\Exceptions\PterodactylException; -use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\Routing\Exception\InvalidParameterException; +use Pterodactyl\Http\Requests\Api\ApiRequest; -abstract class ApplicationApiRequest extends FormRequest +abstract class ApplicationApiRequest extends ApiRequest { /** - * Tracks if the request has been validated internally or not to avoid - * making duplicate validation calls. - */ - private bool $hasValidated = false; - - /** - * The resource that should be checked when performing the authorization - * function for this request. - * - * @var string|null - */ - protected string $resource; - - /** - * The permission level that a given API key should have for accessing - * the defined $resource during the request cycle. - * - * @var int - */ - protected int $permission = AdminAcl::NONE; - - /** - * Determine if the current user is authorized to perform - * the requested action against the API. - * - * @throws \Pterodactyl\Exceptions\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.'); - } - - if ($this->key()->key_type === ApiKey::TYPE_ACCOUNT) { - return $this->user()->root_admin; - } - - return AdminAcl::check($this->key(), $this->resource, $this->permission); - } - - /** - * Determine if the requested resource exists on the server. - */ - public function resourceExists(): bool - { - return true; - } - - /** - * Default set of rules to apply to API requests. - */ - public function rules(): array - { - return []; - } - - /** - * Return the API key being used for the request. - */ - public function key(): ApiKey - { - return $this->attributes->get('api_key'); - } - - /** - * Grab a model from the route parameters. If no model is found in the - * binding mappings an exception will be thrown. - * - * @return mixed - * - * @deprecated - * - * @throws \Symfony\Component\Routing\Exception\InvalidParameterException - */ - public function getModel(string $model) - { - $parameterKey = array_get(array_flip(ApiSubstituteBindings::getMappings()), $model); - - if (is_null($parameterKey)) { - throw new InvalidParameterException(); - } - - return $this->route()->parameter($parameterKey); - } - - /** - * 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) || !$this->resourceExists()) { - throw new NotFoundHttpException(trans('exceptions.api.resource_not_found')); - } - - return true; + return $this->user()->root_admin; } } diff --git a/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php b/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php index 0efd8e5d0..cde56da1c 100644 --- a/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Databases/DeleteDatabaseRequest.php @@ -2,19 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Databases; -use Pterodactyl\Models\DatabaseHost; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteDatabaseRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_DATABASE_HOSTS; - protected int $permission = AdminAcl::WRITE; - - public function resourceExists(): bool - { - $databaseHost = $this->route()->parameter('databaseHost'); - - return $databaseHost instanceof DatabaseHost && $databaseHost->exists; - } } diff --git a/app/Http/Requests/Api/Application/Databases/GetDatabaseRequest.php b/app/Http/Requests/Api/Application/Databases/GetDatabaseRequest.php index 166064f9f..9392096e3 100644 --- a/app/Http/Requests/Api/Application/Databases/GetDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Databases/GetDatabaseRequest.php @@ -2,14 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Databases; -use Pterodactyl\Models\DatabaseHost; - class GetDatabaseRequest extends GetDatabasesRequest { - public function resourceExists(): bool - { - $databaseHost = $this->route()->parameter('databaseHost'); - - return $databaseHost instanceof DatabaseHost && $databaseHost->exists; - } } diff --git a/app/Http/Requests/Api/Application/Databases/GetDatabasesRequest.php b/app/Http/Requests/Api/Application/Databases/GetDatabasesRequest.php index feb2c8ad0..f55da515b 100644 --- a/app/Http/Requests/Api/Application/Databases/GetDatabasesRequest.php +++ b/app/Http/Requests/Api/Application/Databases/GetDatabasesRequest.php @@ -2,11 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Databases; -use Pterodactyl\Services\Acl\Api\AdminAcl as Acl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetDatabasesRequest extends ApplicationApiRequest { - protected string $resource = Acl::RESOURCE_DATABASE_HOSTS; - protected int $permission = Acl::READ; } diff --git a/app/Http/Requests/Api/Application/Databases/StoreDatabaseRequest.php b/app/Http/Requests/Api/Application/Databases/StoreDatabaseRequest.php index 600a93efa..2bfce7dbb 100644 --- a/app/Http/Requests/Api/Application/Databases/StoreDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Databases/StoreDatabaseRequest.php @@ -3,14 +3,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Databases; use Pterodactyl\Models\DatabaseHost; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreDatabaseRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_DATABASE_HOSTS; - protected int $permission = AdminAcl::WRITE; - public function rules(array $rules = null): array { return $rules ?? DatabaseHost::getRules(); diff --git a/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php b/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php index a3ce1f2c3..154f06efd 100644 --- a/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php +++ b/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php @@ -3,14 +3,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Eggs; use Pterodactyl\Models\Egg; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteEggRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_EGGS; - protected int $permission = AdminAcl::WRITE; - public function resourceExists(): bool { $egg = $this->route()->parameter('egg'); diff --git a/app/Http/Requests/Api/Application/Eggs/GetEggRequest.php b/app/Http/Requests/Api/Application/Eggs/GetEggRequest.php index a01d6db37..ba79e08b0 100644 --- a/app/Http/Requests/Api/Application/Eggs/GetEggRequest.php +++ b/app/Http/Requests/Api/Application/Eggs/GetEggRequest.php @@ -2,14 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Eggs; -use Pterodactyl\Models\Egg; - class GetEggRequest extends GetEggsRequest { - public function resourceExists(): bool - { - $egg = $this->route()->parameter('egg'); - - return $egg instanceof Egg && $egg->exists; - } } diff --git a/app/Http/Requests/Api/Application/Eggs/GetEggsRequest.php b/app/Http/Requests/Api/Application/Eggs/GetEggsRequest.php index fb2015a3f..4b18cd02c 100644 --- a/app/Http/Requests/Api/Application/Eggs/GetEggsRequest.php +++ b/app/Http/Requests/Api/Application/Eggs/GetEggsRequest.php @@ -2,11 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\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; } diff --git a/app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php b/app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php index 7b3cad7e5..6759615c9 100644 --- a/app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php +++ b/app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php @@ -3,14 +3,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Eggs; use Pterodactyl\Models\Egg; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreEggRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_EGGS; - protected int $permission = AdminAcl::WRITE; - public function rules(array $rules = null): array { return $rules ?? Egg::getRules(); diff --git a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php index ba97ec198..eb2cffd34 100644 --- a/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/DeleteLocationRequest.php @@ -2,19 +2,8 @@ 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 DeleteLocationRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_LOCATIONS; - protected int $permission = AdminAcl::WRITE; - - public function resourceExists(): bool - { - $location = $this->route()->parameter('location'); - - return $location instanceof Location && $location->exists; - } } diff --git a/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php b/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php index 59c3605dd..dea82db33 100644 --- a/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/GetLocationRequest.php @@ -2,14 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Locations; -use Pterodactyl\Models\Location; - class GetLocationRequest extends GetLocationsRequest { - public function resourceExists(): bool - { - $location = $this->route()->parameter('location'); - - return $location instanceof Location && $location->exists; - } } diff --git a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php index 1a79846ef..dea300b91 100644 --- a/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php +++ b/app/Http/Requests/Api/Application/Locations/GetLocationsRequest.php @@ -2,11 +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; } diff --git a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php index 08f9a1da6..9b403fa10 100644 --- a/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/StoreLocationRequest.php @@ -3,14 +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; - public function rules(): array { return collect(Location::getRules())->only([ diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php index 6229870ba..91ece11fe 100644 --- a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -6,13 +6,6 @@ use Pterodactyl\Models\Location; class UpdateLocationRequest extends StoreLocationRequest { - public function resourceExists(): bool - { - $location = $this->route()->parameter('location'); - - return $location instanceof Location && $location->exists; - } - public function rules(): array { $locationId = $this->route()->parameter('location')->id; diff --git a/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php b/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php index 0bef09b67..1325510f2 100644 --- a/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php +++ b/app/Http/Requests/Api/Application/Mounts/DeleteMountRequest.php @@ -2,19 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Mounts; -use Pterodactyl\Models\Mount; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteMountRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_MOUNTS; - protected int $permission = AdminAcl::WRITE; - - public function resourceExists(): bool - { - $mount = $this->route()->parameter('mount'); - - return $mount instanceof Mount && $mount->exists; - } } diff --git a/app/Http/Requests/Api/Application/Mounts/GetMountRequest.php b/app/Http/Requests/Api/Application/Mounts/GetMountRequest.php index e6fa96b26..aa30dee02 100644 --- a/app/Http/Requests/Api/Application/Mounts/GetMountRequest.php +++ b/app/Http/Requests/Api/Application/Mounts/GetMountRequest.php @@ -2,14 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Mounts; -use Pterodactyl\Models\Mount; - class GetMountRequest extends GetMountsRequest { - public function resourceExists(): bool - { - $mount = $this->route()->parameter('mount'); - - return $mount instanceof Mount && $mount->exists; - } } diff --git a/app/Http/Requests/Api/Application/Mounts/GetMountsRequest.php b/app/Http/Requests/Api/Application/Mounts/GetMountsRequest.php index c56268625..28fe6ddbc 100644 --- a/app/Http/Requests/Api/Application/Mounts/GetMountsRequest.php +++ b/app/Http/Requests/Api/Application/Mounts/GetMountsRequest.php @@ -2,11 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Mounts; -use Pterodactyl\Services\Acl\Api\AdminAcl as Acl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetMountsRequest extends ApplicationApiRequest { - protected string $resource = Acl::RESOURCE_MOUNTS; - protected int $permission = Acl::READ; } diff --git a/app/Http/Requests/Api/Application/Mounts/MountEggsRequest.php b/app/Http/Requests/Api/Application/Mounts/MountEggsRequest.php index 504247a2e..c29180824 100644 --- a/app/Http/Requests/Api/Application/Mounts/MountEggsRequest.php +++ b/app/Http/Requests/Api/Application/Mounts/MountEggsRequest.php @@ -2,14 +2,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Mounts; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class MountEggsRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_MOUNTS; - protected int $permission = AdminAcl::WRITE; - public function rules(array $rules = null): array { return $rules ?? ['eggs' => 'required|exists:eggs,id']; diff --git a/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php b/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php index 5922677e6..4810591a8 100644 --- a/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php +++ b/app/Http/Requests/Api/Application/Mounts/MountNodesRequest.php @@ -2,14 +2,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Mounts; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class MountNodesRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_MOUNTS; - protected int $permission = AdminAcl::WRITE; - public function rules(array $rules = null): array { return $rules ?? ['nodes' => 'required|exists:nodes,id']; diff --git a/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php b/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php index e6e4f423f..ba678d186 100644 --- a/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php +++ b/app/Http/Requests/Api/Application/Mounts/StoreMountRequest.php @@ -3,14 +3,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Mounts; use Pterodactyl\Models\Mount; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreMountRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_MOUNTS; - protected int $permission = AdminAcl::WRITE; - public function rules(array $rules = null): array { return $rules ?? Mount::getRules(); diff --git a/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php b/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php index 92fbde617..8d505c93e 100644 --- a/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php +++ b/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php @@ -2,19 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nests; -use Pterodactyl\Models\Nest; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteNestRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_NESTS; - protected int $permission = AdminAcl::WRITE; - - public function resourceExists(): bool - { - $nest = $this->route()->parameter('nest'); - - return $nest instanceof Nest && $nest->exists; - } } diff --git a/app/Http/Requests/Api/Application/Nests/GetNestRequest.php b/app/Http/Requests/Api/Application/Nests/GetNestRequest.php index 370d7eab0..efc220dca 100644 --- a/app/Http/Requests/Api/Application/Nests/GetNestRequest.php +++ b/app/Http/Requests/Api/Application/Nests/GetNestRequest.php @@ -2,14 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nests; -use Pterodactyl\Models\Nest; - class GetNestRequest extends GetNestsRequest { - public function resourceExists(): bool - { - $nest = $this->route()->parameter('nest'); - - return $nest instanceof Nest && $nest->exists; - } } diff --git a/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php b/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php index ec04e8edb..899f15101 100644 --- a/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php +++ b/app/Http/Requests/Api/Application/Nests/GetNestsRequest.php @@ -2,11 +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; } diff --git a/app/Http/Requests/Api/Application/Nests/StoreNestRequest.php b/app/Http/Requests/Api/Application/Nests/StoreNestRequest.php index 5be1f4be9..abb72dc5c 100644 --- a/app/Http/Requests/Api/Application/Nests/StoreNestRequest.php +++ b/app/Http/Requests/Api/Application/Nests/StoreNestRequest.php @@ -2,17 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nests; -use Pterodactyl\Models\Nest; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreNestRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_NESTS; - protected int $permission = AdminAcl::WRITE; - - public function rules(array $rules = null): array - { - return $rules ?? Nest::getRules(); - } } diff --git a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php index f9806ecbf..4bd5159d0 100644 --- a/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/DeleteNodeRequest.php @@ -2,19 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Models\Node; -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; - - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - - return $node instanceof Node && $node->exists; - } } diff --git a/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php index d8098a0d9..6d231bc97 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetNodeRequest.php @@ -2,14 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; -use Pterodactyl\Models\Node; - class GetNodeRequest extends GetNodesRequest { - public function resourceExists(): bool - { - $node = $this->route()->parameter('node'); - - return $node instanceof Node && $node->exists; - } } diff --git a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php index 4df2c8523..ac6191ea5 100644 --- a/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/GetNodesRequest.php @@ -2,11 +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; } diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index 9ce8b37de..6ff6b0170 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -3,14 +3,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nodes; 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. */ diff --git a/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php b/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php index 02d042aca..5f6cd34b5 100644 --- a/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php +++ b/app/Http/Requests/Api/Application/Roles/DeleteRoleRequest.php @@ -2,19 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Roles; -use Pterodactyl\Models\AdminRole; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class DeleteRoleRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_ROLES; - protected int $permission = AdminAcl::WRITE; - - public function resourceExists(): bool - { - $role = $this->route()->parameter('role'); - - return $role instanceof AdminRole && $role->exists; - } } diff --git a/app/Http/Requests/Api/Application/Roles/GetRoleRequest.php b/app/Http/Requests/Api/Application/Roles/GetRoleRequest.php index 287323ed6..f8c71d954 100644 --- a/app/Http/Requests/Api/Application/Roles/GetRoleRequest.php +++ b/app/Http/Requests/Api/Application/Roles/GetRoleRequest.php @@ -2,14 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Roles; -use Pterodactyl\Models\AdminRole; - class GetRoleRequest extends GetRolesRequest { - public function resourceExists(): bool - { - $role = $this->route()->parameter('role'); - - return $role instanceof AdminRole && $role->exists; - } } diff --git a/app/Http/Requests/Api/Application/Roles/GetRolesRequest.php b/app/Http/Requests/Api/Application/Roles/GetRolesRequest.php index 3384b3efd..89b5e5fb0 100644 --- a/app/Http/Requests/Api/Application/Roles/GetRolesRequest.php +++ b/app/Http/Requests/Api/Application/Roles/GetRolesRequest.php @@ -2,11 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Roles; -use Pterodactyl\Services\Acl\Api\AdminAcl as Acl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetRolesRequest extends ApplicationApiRequest { - protected string $resource = Acl::RESOURCE_ROLES; - protected int $permission = Acl::READ; } diff --git a/app/Http/Requests/Api/Application/Roles/StoreRoleRequest.php b/app/Http/Requests/Api/Application/Roles/StoreRoleRequest.php index 7e1b6521c..5690dd683 100644 --- a/app/Http/Requests/Api/Application/Roles/StoreRoleRequest.php +++ b/app/Http/Requests/Api/Application/Roles/StoreRoleRequest.php @@ -3,14 +3,10 @@ namespace Pterodactyl\Http\Requests\Api\Application\Roles; use Pterodactyl\Models\AdminRole; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreRoleRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_ROLES; - protected int $permission = AdminAcl::WRITE; - public function rules(array $rules = null): array { return $rules ?? AdminRole::getRules(); diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php index 66e5781a0..dd1dd2fd1 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabaseRequest.php @@ -2,19 +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; - - public function resourceExists(): bool - { - $server = $this->route()->parameter('server'); - $database = $this->route()->parameter('database'); - - return $database->server_id === $server->id; - } } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php index 22349eb6e..74f942278 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/GetServerDatabasesRequest.php @@ -2,11 +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; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php index 66cec82c3..827d68c55 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/ServerDatabaseWriteRequest.php @@ -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; } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php index 69413a14f..f8549075a 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php @@ -6,15 +6,11 @@ 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; - public function rules(): array { $server = $this->route()->parameter('server'); diff --git a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php index bcbd87da0..790f55798 100644 --- a/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetExternalServerRequest.php @@ -2,35 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; -use Pterodactyl\Models\Server; -use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalServerRequest extends ApplicationApiRequest { - private Server $serverModel; - protected string $resource = AdminAcl::RESOURCE_SERVERS; - protected int $permission = AdminAcl::READ; - - public function resourceExists(): bool - { - $repository = $this->container->make(ServerRepositoryInterface::class); - - try { - $this->serverModel = $repository->findFirstWhere([ - ['external_id', '=', $this->route()->parameter('external_id')], - ]); - } catch (RecordNotFoundException $exception) { - return false; - } - - return true; - } - - public function getServerModel(): Server - { - return $this->serverModel; - } } diff --git a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php index adc79c2fd..2f4f417cd 100644 --- a/app/Http/Requests/Api/Application/Servers/GetServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/GetServerRequest.php @@ -2,11 +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; } diff --git a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php index 2d5ab7d84..e8d01a115 100644 --- a/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php +++ b/app/Http/Requests/Api/Application/Servers/ServerWriteRequest.php @@ -2,11 +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; } diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index 737dd54a3..3e42ab62a 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -4,16 +4,12 @@ namespace Pterodactyl\Http\Requests\Api\Application\Servers; use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; -use Pterodactyl\Services\Acl\Api\AdminAcl; use Illuminate\Contracts\Validation\Validator; 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; - public function rules(): array { $rules = Server::getRules(); @@ -93,7 +89,9 @@ class StoreServerRequest extends ApplicationApiRequest public function withValidator(Validator $validator) { $validator->sometimes('allocation.default', [ - 'required', 'integer', 'bail', + 'required', + 'integer', + 'bail', Rule::exists('allocations', 'id')->where(function ($query) { $query->whereNull('server_id'); }), diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index 7cfb0ff37..05d9bb0f6 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -3,14 +3,10 @@ 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; - public function rules(): array { $data = Server::getRulesForUpdate($this->route()->parameter('server')->id); diff --git a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php index 185277614..a2e3841fb 100644 --- a/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/DeleteUserRequest.php @@ -2,19 +2,8 @@ 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 DeleteUserRequest extends ApplicationApiRequest { - protected string $resource = AdminAcl::RESOURCE_USERS; - protected int $permission = AdminAcl::WRITE; - - public function resourceExists(): bool - { - $user = $this->route()->parameter('user'); - - return $user instanceof User && $user->exists; - } } diff --git a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php index d432ae3d9..b26ef7661 100644 --- a/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetExternalUserRequest.php @@ -2,35 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Models\User; -use Pterodactyl\Services\Acl\Api\AdminAcl; -use Pterodactyl\Contracts\Repository\UserRepositoryInterface; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class GetExternalUserRequest extends ApplicationApiRequest { - private User $userModel; - protected string $resource = AdminAcl::RESOURCE_USERS; - protected int $permission = AdminAcl::READ; - - public function resourceExists(): bool - { - $repository = $this->container->make(UserRepositoryInterface::class); - - try { - $this->userModel = $repository->findFirstWhere([ - ['external_id', '=', $this->route()->parameter('external_id')], - ]); - } catch (RecordNotFoundException $exception) { - return false; - } - - return true; - } - - public function getUserModel(): User - { - return $this->userModel; - } } diff --git a/app/Http/Requests/Api/Application/Users/GetUserRequest.php b/app/Http/Requests/Api/Application/Users/GetUserRequest.php index 417cfd59a..4e16088a5 100644 --- a/app/Http/Requests/Api/Application/Users/GetUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetUserRequest.php @@ -2,14 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Application\Users; -use Pterodactyl\Models\User; - class GetUserRequest extends GetUsersRequest { - public function resourceExists(): bool - { - $user = $this->route()->parameter('user'); - - return $user instanceof User && $user->exists; - } } diff --git a/app/Http/Requests/Api/Application/Users/GetUsersRequest.php b/app/Http/Requests/Api/Application/Users/GetUsersRequest.php index 406883f7e..466eaf1aa 100644 --- a/app/Http/Requests/Api/Application/Users/GetUsersRequest.php +++ b/app/Http/Requests/Api/Application/Users/GetUsersRequest.php @@ -2,11 +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; } diff --git a/app/Http/Requests/Api/Application/Users/StoreUserRequest.php b/app/Http/Requests/Api/Application/Users/StoreUserRequest.php index cdc4255c0..ad20272cc 100644 --- a/app/Http/Requests/Api/Application/Users/StoreUserRequest.php +++ b/app/Http/Requests/Api/Application/Users/StoreUserRequest.php @@ -3,14 +3,10 @@ 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; - public function rules(array $rules = null): array { $rules = $rules ?? User::getRules(); diff --git a/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php b/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php index c2a21c4c0..b4405e3e9 100644 --- a/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php +++ b/app/Http/Requests/Api/Client/Account/StoreApiKeyRequest.php @@ -2,29 +2,15 @@ namespace Pterodactyl\Http\Requests\Api\Client\Account; -use Pterodactyl\Models\ApiKey; -use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; +use Pterodactyl\Models\PersonalAccessToken; +use Pterodactyl\Http\Requests\Api\Client\AccountApiRequest; -class StoreApiKeyRequest extends ClientApiRequest +class StoreApiKeyRequest extends AccountApiRequest { public function rules(): array { - $rules = ApiKey::getRules(); - return [ - 'description' => $rules['memo'], - 'allowed_ips' => $rules['allowed_ips'], - 'allowed_ips.*' => 'ip', - ]; - } - - /** - * @return array|string[] - */ - public function messages() - { - return [ - 'allowed_ips.*' => 'All of the IP addresses entered must be valid IPv4 addresses.', + 'description' => PersonalAccessToken::getRules()['description'], ]; } } diff --git a/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php index 359b3400d..ed5df22c4 100644 --- a/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php +++ b/app/Http/Requests/Api/Client/Account/StoreSSHKeyRequest.php @@ -3,9 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Account; use Pterodactyl\Models\UserSSHKey; -use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; +use Pterodactyl\Http\Requests\Api\Client\AccountApiRequest; -class StoreSSHKeyRequest extends ClientApiRequest +class StoreSSHKeyRequest extends AccountApiRequest { public function rules(): array { diff --git a/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php index 193d2fd70..25f885ce3 100644 --- a/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php +++ b/app/Http/Requests/Api/Client/Account/UpdateEmailRequest.php @@ -3,20 +3,16 @@ namespace Pterodactyl\Http\Requests\Api\Client\Account; use Pterodactyl\Models\User; -use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; +use Pterodactyl\Http\Requests\Api\Client\AccountApiRequest; use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException; -class UpdateEmailRequest extends ClientApiRequest +class UpdateEmailRequest extends AccountApiRequest { /** * @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException */ public function authorize(): bool { - if (!parent::authorize()) { - return false; - } - // Verify password matches when changing password or email. if (!password_verify($this->input('password'), $this->user()->password)) { throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password')); diff --git a/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php index c0e27bdb5..56d771338 100644 --- a/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php +++ b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php @@ -2,20 +2,16 @@ namespace Pterodactyl\Http\Requests\Api\Client\Account; -use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; +use Pterodactyl\Http\Requests\Api\Client\AccountApiRequest; use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException; -class UpdatePasswordRequest extends ClientApiRequest +class UpdatePasswordRequest extends AccountApiRequest { /** * @throws \Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException */ public function authorize(): bool { - if (!parent::authorize()) { - return false; - } - // Verify password matches when changing password or email. if (!password_verify($this->input('current_password'), $this->user()->password)) { throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password')); diff --git a/app/Http/Requests/Api/Client/AccountApiRequest.php b/app/Http/Requests/Api/Client/AccountApiRequest.php new file mode 100644 index 000000000..5eb087b8e --- /dev/null +++ b/app/Http/Requests/Api/Client/AccountApiRequest.php @@ -0,0 +1,16 @@ +route()->parameter('server'); + $server = $this->route()->parameter('server'); - if ($server instanceof Server) { - return $this->user()->can($this->permission(), $server); - } - - // If there is no server available on the reqest, trigger a failure since - // we expect there to be one at this point. - return false; + if ($server instanceof Server) { + return $this->user()->can($this->permission(), $server); } - return true; + // If there is no server available on the reqest, trigger a failure since + // we expect there to be one at this point. + return false; } } diff --git a/app/Http/Requests/Api/Client/GetServersRequest.php b/app/Http/Requests/Api/Client/GetServersRequest.php index 28b4c2ded..02d9bf5b5 100644 --- a/app/Http/Requests/Api/Client/GetServersRequest.php +++ b/app/Http/Requests/Api/Client/GetServersRequest.php @@ -2,10 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Client; -class GetServersRequest extends ClientApiRequest +class GetServersRequest extends AccountApiRequest { - public function authorize(): bool - { - return true; - } } diff --git a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php index 5fbdaf728..2871c039c 100644 --- a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php @@ -7,10 +7,7 @@ use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class StoreBackupRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_BACKUP_CREATE; } diff --git a/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php index 92b14157a..f6ccaa3bf 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/DeleteDatabaseRequest.php @@ -2,21 +2,13 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Databases; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Database; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class DeleteDatabaseRequest extends ClientApiRequest implements ClientPermissionsRequest +class DeleteDatabaseRequest extends ClientApiRequest { public function permission(): string { return Permission::ACTION_DATABASE_DELETE; } - - public function resourceExists(): bool - { - return $this->getModel(Server::class)->id === $this->getModel(Database::class)->server_id; - } } diff --git a/app/Http/Requests/Api/Client/Servers/Databases/GetDatabasesRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/GetDatabasesRequest.php index 80eabf945..383285e1a 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/GetDatabasesRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/GetDatabasesRequest.php @@ -3,10 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Databases; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class GetDatabasesRequest extends ClientApiRequest implements ClientPermissionsRequest +class GetDatabasesRequest extends ClientApiRequest { public function permission(): string { diff --git a/app/Http/Requests/Api/Client/Servers/Databases/RotatePasswordRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/RotatePasswordRequest.php index b2a9163ad..b7ce7210e 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/RotatePasswordRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/RotatePasswordRequest.php @@ -7,9 +7,6 @@ use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class RotatePasswordRequest extends ClientApiRequest { - /** - * Check that the user has permission to rotate the password. - */ public function permission(): string { return Permission::ACTION_DATABASE_UPDATE; diff --git a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php index b8f12bd8a..d93b02821 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php @@ -7,11 +7,10 @@ use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; use Pterodactyl\Models\Permission; use Illuminate\Database\Query\Builder; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Services\Databases\DatabaseManagementService; -class StoreDatabaseRequest extends ClientApiRequest implements ClientPermissionsRequest +class StoreDatabaseRequest extends ClientApiRequest { public function permission(): string { diff --git a/app/Http/Requests/Api/Client/Servers/Files/ChmodFilesRequest.php b/app/Http/Requests/Api/Client/Servers/Files/ChmodFilesRequest.php index bf94cb6ba..bb4e4365d 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/ChmodFilesRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/ChmodFilesRequest.php @@ -3,10 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class ChmodFilesRequest extends ClientApiRequest implements ClientPermissionsRequest +class ChmodFilesRequest extends ClientApiRequest { public function permission(): string { diff --git a/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php b/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php index 3d6e96aac..993f67a2b 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/CompressFilesRequest.php @@ -7,9 +7,6 @@ use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class CompressFilesRequest extends ClientApiRequest { - /** - * Checks that the authenticated user is allowed to create archives for this server. - */ public function permission(): string { return Permission::ACTION_FILE_ARCHIVE; diff --git a/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php index 686c23a23..0bf32bc92 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/CopyFileRequest.php @@ -3,10 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class CopyFileRequest extends ClientApiRequest implements ClientPermissionsRequest +class CopyFileRequest extends ClientApiRequest { public function permission(): string { diff --git a/app/Http/Requests/Api/Client/Servers/Files/CreateFolderRequest.php b/app/Http/Requests/Api/Client/Servers/Files/CreateFolderRequest.php index e4655575d..eaf1fc130 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/CreateFolderRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/CreateFolderRequest.php @@ -7,9 +7,6 @@ use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class CreateFolderRequest extends ClientApiRequest { - /** - * Checks that the authenticated user is allowed to create files on the server. - */ public function permission(): string { return Permission::ACTION_FILE_CREATE; diff --git a/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php index 10db32811..ea700378a 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/DeleteFileRequest.php @@ -3,10 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class DeleteFileRequest extends ClientApiRequest implements ClientPermissionsRequest +class DeleteFileRequest extends ClientApiRequest { public function permission(): string { diff --git a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php deleted file mode 100644 index 0386b8555..000000000 --- a/app/Http/Requests/Api/Client/Servers/Files/DownloadFileRequest.php +++ /dev/null @@ -1,18 +0,0 @@ -user()->can('file.read', $this->getModel(Server::class)); - } -} diff --git a/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php b/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php index ea6ed7b69..4b8a3d4f5 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php @@ -3,16 +3,10 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class GetFileContentsRequest extends ClientApiRequest implements ClientPermissionsRequest +class GetFileContentsRequest extends ClientApiRequest { - /** - * Returns the permissions string indicating which permission should be used to - * validate that the authenticated user has permission to perform this action aganist - * the given resource (server). - */ public function permission(): string { return Permission::ACTION_FILE_READ_CONTENT; diff --git a/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php index cfcaf29bb..c8c686b1a 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/PullFileRequest.php @@ -3,10 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class PullFileRequest extends ClientApiRequest implements ClientPermissionsRequest +class PullFileRequest extends ClientApiRequest { public function permission(): string { diff --git a/app/Http/Requests/Api/Client/Servers/Files/RenameFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/RenameFileRequest.php index 61c4a0c5f..90852d8c4 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/RenameFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/RenameFileRequest.php @@ -3,10 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class RenameFileRequest extends ClientApiRequest implements ClientPermissionsRequest +class RenameFileRequest extends ClientApiRequest { /** * The permission the user is required to have in order to perform this diff --git a/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php b/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php index 6808a5497..a591fdf68 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/UploadFileRequest.php @@ -7,10 +7,7 @@ use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class UploadFileRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_FILE_CREATE; } diff --git a/app/Http/Requests/Api/Client/Servers/Files/WriteFileContentRequest.php b/app/Http/Requests/Api/Client/Servers/Files/WriteFileContentRequest.php index 82a994c36..bd43dd6c1 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/WriteFileContentRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/WriteFileContentRequest.php @@ -3,10 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class WriteFileContentRequest extends ClientApiRequest implements ClientPermissionsRequest +class WriteFileContentRequest extends ClientApiRequest { /** * Returns the permissions string indicating which permission should be used to diff --git a/app/Http/Requests/Api/Client/Servers/GetServerRequest.php b/app/Http/Requests/Api/Client/Servers/GetServerRequest.php index 3798d77ab..bf8f9c78f 100644 --- a/app/Http/Requests/Api/Client/Servers/GetServerRequest.php +++ b/app/Http/Requests/Api/Client/Servers/GetServerRequest.php @@ -2,17 +2,8 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers; -use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; +use Pterodactyl\Http\Requests\Api\Client\AccountApiRequest; -class GetServerRequest extends ClientApiRequest +class GetServerRequest extends AccountApiRequest { - /** - * Determine if a client has permission to view this server on the API. This - * should never be false since this would be checking the same permission as - * resourceExists(). - */ - public function authorize(): bool - { - return true; - } } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php index 9edc8ad1c..63f01dbae 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/ReinstallServerRequest.php @@ -7,10 +7,7 @@ use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class ReinstallServerRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_SETTINGS_REINSTALL; } diff --git a/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php index 8cb5efa35..2c9eb8178 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/RenameServerRequest.php @@ -4,10 +4,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers\Settings; use Pterodactyl\Models\Server; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class RenameServerRequest extends ClientApiRequest implements ClientPermissionsRequest +class RenameServerRequest extends ClientApiRequest { /** * Returns the permissions string indicating which permission should be used to diff --git a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php index bd3a1e65f..237de4f57 100644 --- a/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Settings/SetDockerImageRequest.php @@ -6,10 +6,9 @@ use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Validation\Rule; use Pterodactyl\Models\Permission; -use Pterodactyl\Contracts\Http\ClientPermissionsRequest; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class SetDockerImageRequest extends ClientApiRequest implements ClientPermissionsRequest +class SetDockerImageRequest extends ClientApiRequest { public function permission(): string { diff --git a/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php b/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php index 25ab2ce21..fee92bcbc 100644 --- a/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Startup/GetStartupRequest.php @@ -7,10 +7,7 @@ use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class GetStartupRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_STARTUP_READ; } diff --git a/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php b/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php index b46e6ea9a..9859c8a6d 100644 --- a/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Startup/UpdateStartupVariableRequest.php @@ -7,10 +7,7 @@ use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; class UpdateStartupVariableRequest extends ClientApiRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_STARTUP_UPDATE; } diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php index ef0a09aaa..eabd84e61 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/DeleteSubuserRequest.php @@ -6,10 +6,7 @@ use Pterodactyl\Models\Permission; class DeleteSubuserRequest extends SubuserRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_USER_DELETE; } diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php index 5bc93ab2f..6e8722516 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/StoreSubuserRequest.php @@ -6,10 +6,7 @@ use Pterodactyl\Models\Permission; class StoreSubuserRequest extends SubuserRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_USER_CREATE; } diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php index 997b2daee..bd8929a98 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/UpdateSubuserRequest.php @@ -6,10 +6,7 @@ use Pterodactyl\Models\Permission; class UpdateSubuserRequest extends SubuserRequest { - /** - * @return string - */ - public function permission() + public function permission(): string { return Permission::ACTION_USER_UPDATE; } diff --git a/app/Http/Requests/Api/Client/WebsocketTokenRequest.php b/app/Http/Requests/Api/Client/WebsocketTokenRequest.php new file mode 100644 index 000000000..3849b6e88 --- /dev/null +++ b/app/Http/Requests/Api/Client/WebsocketTokenRequest.php @@ -0,0 +1,19 @@ + 'required|string', 'password' => 'required|string', - 'type' => 'required|in:password,publicKey' + 'type' => 'required|in:password,publicKey', ]; } diff --git a/app/Http/Requests/FrontendUserFormRequest.php b/app/Http/Requests/FrontendUserFormRequest.php deleted file mode 100644 index b5553dc16..000000000 --- a/app/Http/Requests/FrontendUserFormRequest.php +++ /dev/null @@ -1,40 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Requests; - -use Illuminate\Foundation\Http\FormRequest; - -abstract class FrontendUserFormRequest extends FormRequest -{ - abstract public function rules(); - - /** - * Determine if a user is authorized to access this endpoint. - * - * @return bool - */ - public function authorize() - { - return !is_null($this->user()); - } - - /** - * Return only the fields that we are interested in from the request. - * This will include empty fields as a null value. - * - * @return array - */ - public function normalize() - { - return $this->only( - array_keys($this->rules()) - ); - } -} diff --git a/app/Models/APILog.php b/app/Models/APILog.php deleted file mode 100644 index 359daa4ed..000000000 --- a/app/Models/APILog.php +++ /dev/null @@ -1,38 +0,0 @@ - 'boolean', - ]; -} diff --git a/app/Models/AdminRole.php b/app/Models/AdminRole.php index 3f4b44ea2..51b485495 100644 --- a/app/Models/AdminRole.php +++ b/app/Models/AdminRole.php @@ -45,9 +45,6 @@ class AdminRole extends Model 'permissions' => 'array', ]; - /** - * @var array - */ public static array $validationRules = [ 'name' => 'required|string|max:64', 'description' => 'nullable|string|max:255', diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index c01444951..564822abe 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -50,9 +50,6 @@ class Allocation extends Model 'server_id' => 'integer', ]; - /** - * @var array - */ public static array $validationRules = [ 'node_id' => 'required|exists:nodes,id', 'ip' => 'required|ip', diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php deleted file mode 100644 index 80b3df853..000000000 --- a/app/Models/ApiKey.php +++ /dev/null @@ -1,129 +0,0 @@ - 'array', - 'user_id' => 'int', - 'r_' . AdminAcl::RESOURCE_USERS => 'int', - 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int', - 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'int', - 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'int', - 'r_' . AdminAcl::RESOURCE_EGGS => 'int', - 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'int', - 'r_' . AdminAcl::RESOURCE_NESTS => 'int', - 'r_' . AdminAcl::RESOURCE_NODES => 'int', - 'r_' . AdminAcl::RESOURCE_SERVERS => 'int', - 'r_' . AdminAcl::RESOURCE_ROLES => 'int', - ]; - - /** - * Fields that are mass assignable. - * - * @var array - */ - protected $fillable = [ - 'identifier', - 'token', - 'allowed_ips', - 'memo', - 'last_used_at', - ]; - - /** - * Fields that should not be included when calling toArray() or toJson() - * on this model. - * - * @var array - */ - protected $hidden = ['token']; - - /** - * Rules to protect against invalid data entry to DB. - * - * @var array - */ - public static array $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|array', - 'allowed_ips.*' => 'string', - 'last_used_at' => 'nullable|date', - 'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_DATABASE_HOSTS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_SERVER_DATABASES => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_EGGS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_LOCATIONS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_NESTS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_NODES => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3', - 'r_' . AdminAcl::RESOURCE_ROLES => 'integer|min:0|max:3', - ]; - - /** - * @var array - */ - protected $dates = [ - self::CREATED_AT, - self::UPDATED_AT, - 'last_used_at', - ]; -} diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php index 18b466a22..a1ae4ea9d 100644 --- a/app/Models/AuditLog.php +++ b/app/Models/AuditLog.php @@ -61,9 +61,6 @@ class AuditLog extends Model */ protected $table = 'audit_logs'; - /** - * @var bool - */ protected bool $immutableDates = true; /** diff --git a/app/Models/Backup.php b/app/Models/Backup.php index 8b3413147..9b0abde6a 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -37,9 +37,6 @@ class Backup extends Model */ protected $table = 'backups'; - /** - * @var bool - */ protected bool $immutableDates = true; /** @@ -76,9 +73,6 @@ class Backup extends Model */ protected $guarded = ['id', 'created_at', 'updated_at', 'deleted_at']; - /** - * @var array - */ public static array $validationRules = [ 'server_id' => 'bail|required|numeric|exists:servers,id', 'uuid' => 'required|uuid', @@ -107,6 +101,6 @@ class Backup extends Model { return $this->hasMany(AuditLog::class, 'metadata->backup_uuid', 'uuid') ->where('action', 'LIKE', 'server:backup.%'); - // ->where('metadata->backup_uuid', $this->uuid); + // ->where('metadata->backup_uuid', $this->uuid); } } diff --git a/app/Models/Database.php b/app/Models/Database.php index 291604632..b2be8a7d2 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -58,9 +58,6 @@ class Database extends Model 'max_connections' => 'integer', ]; - /** - * @var array - */ public static array $validationRules = [ 'server_id' => 'required|numeric|exists:servers,id', 'database_host_id' => 'required|exists:database_hosts,id', diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index f184f313b..31dad3865 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -21,9 +21,6 @@ class DatabaseHost extends Model */ public const RESOURCE_NAME = 'database_host'; - /** - * @var bool - */ protected bool $immutableDates = true; /** @@ -61,8 +58,6 @@ class DatabaseHost extends Model /** * Validation rules to assign to this model. - * - * @var array */ public static array $validationRules = [ 'name' => 'required|string|max:191', diff --git a/app/Models/Egg.php b/app/Models/Egg.php index c8698e05a..992a54f91 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -108,9 +108,6 @@ class Egg extends Model 'file_denylist' => 'array', ]; - /** - * @var array - */ public static array $validationRules = [ 'nest_id' => 'required|bail|numeric|exists:nests,id', 'uuid' => 'required|string|size:36', diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index 0a48132d7..98e3da2c9 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -37,9 +37,6 @@ class EggVariable extends Model */ public const RESERVED_ENV_NAMES = 'SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,SERVER_UUID,UUID'; - /** - * @var bool - */ protected bool $immutableDates = true; /** @@ -67,9 +64,6 @@ class EggVariable extends Model 'user_editable' => 'bool', ]; - /** - * @var array - */ public static array $validationRules = [ 'egg_id' => 'exists:eggs,id', 'name' => 'required|string|between:1,191', diff --git a/app/Models/Location.php b/app/Models/Location.php index df04eb417..ad9ae2a21 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -35,8 +35,6 @@ class Location extends Model /** * Rules ensuring that the raw data stored in the database meets expectations. - * - * @var array */ public static array $validationRules = [ 'short' => 'required|string|between:1,60|unique:locations,short', diff --git a/app/Models/Model.php b/app/Models/Model.php index 184b3d1c1..8e762801a 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -17,8 +17,6 @@ abstract class Model extends IlluminateModel /** * Set to true to return immutable Carbon date instances from the model. - * - * @var bool */ protected bool $immutableDates = false; @@ -42,9 +40,6 @@ abstract class Model extends IlluminateModel */ protected static $validatorFactory; - /** - * @var array - */ public static array $validationRules = []; /** @@ -82,8 +77,6 @@ abstract class Model extends IlluminateModel /** * Returns the validator instance used by this model. - * - * @return \Illuminate\Validation\Validator */ public function getValidator(): Validator { diff --git a/app/Models/Node.php b/app/Models/Node.php index 052b352a2..f0bee4d67 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -152,6 +152,7 @@ class Node extends Model * Returns the configuration as an array. * * @return array + * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getConfiguration() @@ -186,6 +187,7 @@ class Node extends Model * Returns the configuration in Yaml format. * * @return string + * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getYamlConfiguration() @@ -197,6 +199,7 @@ class Node extends Model * Returns the configuration in JSON format. * * @return string + * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getJsonConfiguration(bool $pretty = false) diff --git a/app/Models/Permission.php b/app/Models/Permission.php index aa505398b..b3b127486 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -93,9 +93,6 @@ class Permission extends Model 'subuser_id' => 'integer', ]; - /** - * @var array - */ public static array $validationRules = [ 'subuser_id' => 'required|numeric|min:1', 'permission' => 'required|string', diff --git a/app/Models/PersonalAccessToken.php b/app/Models/PersonalAccessToken.php new file mode 100644 index 000000000..56ca18cea --- /dev/null +++ b/app/Models/PersonalAccessToken.php @@ -0,0 +1,117 @@ + 'int', + 'abilities' => 'json', + 'last_used_at' => 'datetime', + ]; + + /** + * @var string[] + */ + protected $fillable = [ + 'description', + 'token', + 'token_id', + 'abilities', + ]; + + public static array $validationRules = [ + 'token' => 'required|string', + 'token_id' => 'required|string|size:16', + 'description' => 'required|nullable|string|max:500', + 'last_used_at' => 'nullable|date', + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo(User::class); + } + + /** + * Required for support with Laravel Sanctum. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * + * @see \Laravel\Sanctum\Guard::supportsTokens() + */ + public function tokenable() + { + return $this->user(); + } + + /** + * Determine if the token has a given ability. + * + * @param string $ability + * + * @return bool + */ + public function can($ability) + { + return in_array('*', $this->abilities) || + array_key_exists($ability, array_flip($this->abilities)); + } + + /** + * Determine if the token is missing a given ability. + * + * @param string $ability + * + * @return bool + */ + public function cant($ability) + { + return !$this->can($ability); + } + + /** + * Find the token instance matching the given token. + * + * @param string $token + * + * @return \Pterodactyl\Models\PersonalAccessToken|null + */ + public static function findToken($token) + { + if (strpos($token, '_') === false) { + return null; + } + + $id = Str::substr($token, 0, 16); + $token = Str::substr($token, strlen($id)); + + return static::where('token_id', $id)->where('token', hash('sha256', $token))->first(); + } + + /** + * Generates a new identifier for a personal access token. + */ + public static function generateTokenIdentifier(): string + { + return 'ptdl_' . Str::random(11); + } +} diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php index 0b6a372f4..1645111ed 100644 --- a/app/Models/ServerTransfer.php +++ b/app/Models/ServerTransfer.php @@ -58,9 +58,6 @@ class ServerTransfer extends Model 'archived' => 'bool', ]; - /** - * @var array - */ public static array $validationRules = [ 'server_id' => 'required|numeric|exists:servers,id', 'old_node' => 'required|numeric', diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 9f4396bb1..b620ec8d2 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -21,9 +21,6 @@ class Setting extends Model */ protected $fillable = ['key', 'value']; - /** - * @var array - */ public static array $validationRules = [ 'key' => 'required|string|between:1,191', 'value' => 'string', diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index f69c4c6dc..eaa3de06c 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -4,16 +4,6 @@ namespace Pterodactyl\Models; use Illuminate\Notifications\Notifiable; -/** - * @property int $id - * @property int $user_id - * @property int $server_id - * @property array $permissions - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at - * @property \Pterodactyl\Models\User $user - * @property \Pterodactyl\Models\Server $server - */ class Subuser extends Model { use Notifiable; @@ -49,9 +39,6 @@ class Subuser extends Model 'permissions' => 'array', ]; - /** - * @var array - */ public static array $validationRules = [ 'user_id' => 'required|numeric|exists:users,id', 'server_id' => 'required|numeric|exists:servers,id', diff --git a/app/Models/Task.php b/app/Models/Task.php index 348a9098f..a6de1f3ec 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -92,9 +92,6 @@ class Task extends Model 'continue_on_failure' => false, ]; - /** - * @var array - */ public static array $validationRules = [ 'schedule_id' => 'required|numeric|exists:schedules,id', 'sequence_id' => 'required|numeric|min:1', diff --git a/app/Models/Traits/HasAccessTokens.php b/app/Models/Traits/HasAccessTokens.php new file mode 100644 index 000000000..70929228b --- /dev/null +++ b/app/Models/Traits/HasAccessTokens.php @@ -0,0 +1,42 @@ +hasMany(PersonalAccessToken::class); + } + + /** + * Creates a new personal access token for the user. The token will be returned + * as the first element of the array, and the plain-text token will be the second. + */ + public function createToken(string $description, array $abilities = ['*']): NewAccessToken + { + /** @var \Pterodactyl\Models\PersonalAccessToken $token */ + $token = $this->tokens()->create([ + 'user_id' => $this->id, + 'description' => $description, + 'token' => hash('sha256', $plain = Str::random(PersonalAccessToken::TOKEN_LENGTH)), + 'token_id' => PersonalAccessToken::generateTokenIdentifier(), + 'abilities' => $abilities, + ]); + + return new NewAccessToken($token, $token->token_id . $plain); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 707635e88..3e3ae71d9 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,41 +7,18 @@ use Illuminate\Support\Collection; use Illuminate\Auth\Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Builder; +use Pterodactyl\Models\Traits\HasAccessTokens; use Illuminate\Auth\Passwords\CanResetPassword; -use Pterodactyl\Traits\Helpers\AvailableLanguages; use Illuminate\Database\Eloquent\Relations\HasOne; -use Illuminate\Foundation\Auth\Access\Authorizable; +use Pterodactyl\Traits\Helpers\AvailableLanguages; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Foundation\Auth\Access\Authorizable; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; -/** - * @property int $id - * @property string|null $external_id - * @property string $uuid - * @property string $username - * @property string $email - * @property string $password - * @property string|null $remember_token - * @property string $language - * @property int $admin_role_id - * @property bool $root_admin - * @property bool $use_totp - * @property string|null $totp_secret - * @property \Carbon\Carbon|null $totp_authenticated_at - * @property bool $gravatar - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at - * @property string $name - * @property \Pterodactyl\Models\AdminRole $adminRole - * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys - * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers - * @property \Pterodactyl\Models\UserSSHKey|\Illuminate\Database\Eloquent\Collection $sshKeys - * @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryTokens - * @property \Pterodactyl\Models\WebauthnKey[]|\Illuminate\Database\Eloquent\Collection $webauthnKeys - */ class User extends Model implements AuthenticatableContract, AuthorizableContract, @@ -51,6 +28,8 @@ class User extends Model implements use Authorizable; use AvailableLanguages; use CanResetPassword; + use HasAccessTokens; + use HasFactory; use Notifiable; public const USER_LEVEL_USER = 0; @@ -132,8 +111,6 @@ class User extends Model implements /** * Rules verifying that the data being stored matches the expectations of the database. - * - * @var array */ public static array $validationRules = [ 'uuid' => 'required|string|size:36|unique:users,uuid', @@ -194,8 +171,6 @@ class User extends Model implements /** * Gets the avatar url for the user. - * - * @return string */ public function avatarURL(): string { @@ -204,10 +179,8 @@ class User extends Model implements /** * Gets the name of the role assigned to a user. - * - * @return string|null */ - public function adminRoleName():? string + public function adminRoleName(): ?string { $role = $this->adminRole; if (is_null($role)) { @@ -222,12 +195,6 @@ class User extends Model implements return $this->hasOne(AdminRole::class, 'id', 'admin_role_id'); } - public function apiKeys(): HasMany - { - return $this->hasMany(ApiKey::class) - ->where('key_type', ApiKey::TYPE_ACCOUNT); - } - public function servers(): HasMany { return $this->hasMany(Server::class, 'owner_id'); diff --git a/app/Models/UserSSHKey.php b/app/Models/UserSSHKey.php index 7ecc6730b..c61500164 100644 --- a/app/Models/UserSSHKey.php +++ b/app/Models/UserSSHKey.php @@ -14,7 +14,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; */ class UserSSHKey extends Model { - const UPDATED_AT = null; + /** + * The resource name for this model when it is transformed into an + * API representation using fractal. + */ + public const RESOURCE_NAME = 'user_ssh_key'; + + public const UPDATED_AT = null; protected $table = 'user_ssh_keys'; protected bool $immutableDates = true; diff --git a/app/Models/WebauthnKey.php b/app/Models/WebauthnKey.php index 780508028..07068e407 100644 --- a/app/Models/WebauthnKey.php +++ b/app/Models/WebauthnKey.php @@ -9,6 +9,8 @@ class WebauthnKey extends \LaravelWebauthn\Models\WebauthnKey { use HasFactory; + public const RESOURCE_NAME = 'webauthn_key'; + public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f34237819..f7a18ac5c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Providers; +use Laravel\Sanctum\Sanctum; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; @@ -10,6 +11,7 @@ use Illuminate\Support\ServiceProvider; use Pterodactyl\Observers\UserObserver; use Pterodactyl\Observers\ServerObserver; use Pterodactyl\Observers\SubuserObserver; +use Pterodactyl\Models\PersonalAccessToken; class AppServiceProvider extends ServiceProvider { @@ -23,6 +25,11 @@ class AppServiceProvider extends ServiceProvider User::observe(UserObserver::class); Server::observe(ServerObserver::class); Subuser::observe(SubuserObserver::class); + + /* + * @see https://laravel.com/docs/8.x/sanctum#overriding-default-models + */ + Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); } /** @@ -30,9 +37,11 @@ class AppServiceProvider extends ServiceProvider */ public function register() { + Sanctum::ignoreMigrations(); + // Only load the settings service provider if the environment // is configured to allow it. - if (! config('pterodactyl.load_environment_only', false) && $this->app->environment() !== 'testing') { + if (!config('pterodactyl.load_environment_only', false) && $this->app->environment() !== 'testing') { $this->app->register(SettingsServiceProvider::class); } } diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index 8a0434f52..b91f60ace 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -8,7 +8,6 @@ use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\TaskRepository; use Pterodactyl\Repositories\Eloquent\UserRepository; -use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Eloquent\SessionRepository; use Pterodactyl\Repositories\Eloquent\SubuserRepository; @@ -24,7 +23,6 @@ use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; -use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; use Pterodactyl\Contracts\Repository\SessionRepositoryInterface; @@ -47,7 +45,6 @@ class RepositoryServiceProvider extends ServiceProvider { // Eloquent Repositories $this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class); - $this->app->bind(ApiKeyRepositoryInterface::class, ApiKeyRepository::class); $this->app->bind(DatabaseRepositoryInterface::class, DatabaseRepository::class); $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); $this->app->bind(EggRepositoryInterface::class, EggRepository::class); diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index e6b84ec8f..db07b1627 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -48,7 +48,7 @@ class RouteServiceProvider extends ServiceProvider ->group(base_path('routes/api-application.php')); Route::middleware([ - sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')), + //sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')), 'client-api', ])->prefix('/api/client') ->namespace($this->namespace . '\Api\Client') diff --git a/app/Repositories/Eloquent/ApiKeyRepository.php b/app/Repositories/Eloquent/ApiKeyRepository.php deleted file mode 100644 index 67a7e6e6e..000000000 --- a/app/Repositories/Eloquent/ApiKeyRepository.php +++ /dev/null @@ -1,63 +0,0 @@ -getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_ACCOUNT) - ->get($this->getColumns()); - } - - /** - * Get all of the application API keys that exist for a specific user. - */ - public function getApplicationKeys(User $user): Collection - { - return $this->getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_APPLICATION) - ->get($this->getColumns()); - } - - /** - * Delete an account API key from the panel for a specific user. - */ - public function deleteAccountKey(User $user, string $identifier): int - { - return $this->getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_ACCOUNT) - ->where('identifier', $identifier) - ->delete(); - } - - /** - * Delete an application API key from the panel for a specific user. - */ - public function deleteApplicationKey(User $user, string $identifier): int - { - return $this->getBuilder()->where('user_id', $user->id) - ->where('key_type', ApiKey::TYPE_APPLICATION) - ->where('identifier', $identifier) - ->delete(); - } -} diff --git a/app/Repositories/Eloquent/BackupRepository.php b/app/Repositories/Eloquent/BackupRepository.php index 7efdace06..051d0ce42 100644 --- a/app/Repositories/Eloquent/BackupRepository.php +++ b/app/Repositories/Eloquent/BackupRepository.php @@ -38,8 +38,6 @@ class BackupRepository extends EloquentRepository /** * Returns a query filtering only non-failed backups for a specific server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function getNonFailedBackups(Server $server): HasMany { diff --git a/app/Repositories/Eloquent/NestRepository.php b/app/Repositories/Eloquent/NestRepository.php index 89b9c9800..7e4884255 100644 --- a/app/Repositories/Eloquent/NestRepository.php +++ b/app/Repositories/Eloquent/NestRepository.php @@ -28,8 +28,6 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa /** * Return a nest or all nests with their associated eggs and variables. * - * @param int|null $id - * * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException diff --git a/app/Services/Acl/Api/AdminAcl.php b/app/Services/Acl/Api/AdminAcl.php index 99c2d344f..547e43890 100644 --- a/app/Services/Acl/Api/AdminAcl.php +++ b/app/Services/Acl/Api/AdminAcl.php @@ -2,17 +2,8 @@ namespace Pterodactyl\Services\Acl\Api; -use ReflectionClass; -use Pterodactyl\Models\ApiKey; - class AdminAcl { - /** - * Resource permission columns in the api_keys table begin - * with this identifier. - */ - public const COLUMN_IDENTIFIER = 'r_'; - /** * The different types of permissions available for API keys. This * implements a read/write/none permissions scheme for all endpoints. @@ -36,48 +27,4 @@ class AdminAcl public const RESOURCE_SERVER_DATABASES = 'server_databases'; public const RESOURCE_ROLES = 'roles'; public const RESOURCE_MOUNTS = 'mounts'; - - /** - * Determine if an API key has permission to perform a specific read/write operation. - * - * @param int $permission - * @param int $action - * @return bool - */ - public static function can(int $permission, int $action = self::READ) - { - if ($permission & $action) { - return true; - } - - return false; - } - - /** - * Determine if an API Key model has permission to access a given resource - * at a specific action level. - * - * @param \Pterodactyl\Models\ApiKey $key - * @param string $resource - * @param int $action - * @return bool - */ - public static function check(ApiKey $key, string $resource, int $action = self::READ) - { - return self::can(data_get($key, self::COLUMN_IDENTIFIER . $resource, self::NONE), $action); - } - - /** - * Return a list of all resource constants defined in this ACL. - * - * @return array - */ - public static function getResourceList(): array - { - $reflect = new ReflectionClass(__CLASS__); - - return collect($reflect->getConstants())->filter(function ($value, $key) { - return substr($key, 0, 9) === 'RESOURCE_'; - })->values()->toArray(); - } } diff --git a/app/Services/Api/KeyCreationService.php b/app/Services/Api/KeyCreationService.php deleted file mode 100644 index 20a11add0..000000000 --- a/app/Services/Api/KeyCreationService.php +++ /dev/null @@ -1,69 +0,0 @@ -encrypter = $encrypter; - $this->repository = $repository; - } - - /** - * Set the type of key that should be created. By default an orphaned key will be - * created. These keys cannot be used for anything, and will not render in the UI. - * - * @return \Pterodactyl\Services\Api\KeyCreationService - */ - public function setKeyType(int $type) - { - $this->keyType = $type; - - return $this; - } - - /** - * Create a new API key for the Panel using the permissions passed in the data request. - * This will automatically generate an identifier and an encrypted token that are - * stored in the database. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function handle(array $data, array $permissions = []): ApiKey - { - $data = array_merge($data, [ - 'key_type' => $this->keyType, - 'identifier' => str_random(ApiKey::IDENTIFIER_LENGTH), - 'token' => $this->encrypter->encrypt(str_random(ApiKey::KEY_LENGTH)), - ]); - - if ($this->keyType === ApiKey::TYPE_APPLICATION) { - $data = array_merge($data, $permissions); - } - - return $this->repository->create($data, true, true); - } -} diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php index 718328cfb..634f17cfe 100644 --- a/app/Services/Backups/InitiateBackupService.php +++ b/app/Services/Backups/InitiateBackupService.php @@ -55,11 +55,7 @@ class InitiateBackupService /** * InitiateBackupService constructor. * - * @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository - * @param \Illuminate\Database\ConnectionInterface $connection - * @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository * @param \Pterodactyl\Services\Backups\DeleteBackupService $deleteBackupService - * @param \Pterodactyl\Extensions\Backups\BackupManager $backupManager */ public function __construct( BackupRepository $repository, diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index e6c768d19..f601e6f20 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -43,8 +43,6 @@ class SoftwareVersionService /** * Gets the current version of the panel that is being used. - * - * @return string */ public function getVersion(): string { @@ -53,8 +51,6 @@ class SoftwareVersionService /** * Get the latest version of the panel from the CDN servers. - * - * @return string */ public function getLatestPanel(): string { @@ -63,8 +59,6 @@ class SoftwareVersionService /** * Get the latest version of wings from the CDN servers. - * - * @return string */ public function getLatestWings(): string { @@ -89,8 +83,6 @@ class SoftwareVersionService /** * Determine if a passed wings version is the latest. * - * @param string $version - * * @return bool */ public function isLatestWings(string $version) @@ -127,8 +119,6 @@ class SoftwareVersionService /** * Return version information for the footer. - * - * @return array */ protected function versionData(): array { @@ -159,8 +149,6 @@ class SoftwareVersionService /** * ? - * - * @return array */ public function getVersionData(): array { diff --git a/app/Transformers/Api/Application/AdminRoleTransformer.php b/app/Transformers/Api/Application/AdminRoleTransformer.php index 2c7b434c3..1aca9aebc 100644 --- a/app/Transformers/Api/Application/AdminRoleTransformer.php +++ b/app/Transformers/Api/Application/AdminRoleTransformer.php @@ -3,25 +3,15 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\AdminRole; +use Pterodactyl\Transformers\Api\Transformer; -class AdminRoleTransformer extends BaseTransformer +class AdminRoleTransformer extends Transformer { - /** - * Return the resource name for the JSONAPI output. - * - * @return string - */ public function getResourceName(): string { return AdminRole::RESOURCE_NAME; } - /** - * Return a transformed AdminRole model that can be consumed by external services. - * - * @param \Pterodactyl\Models\AdminRole $model - * @return array - */ public function transform(AdminRole $model): array { return [ diff --git a/app/Transformers/Api/Application/AllocationTransformer.php b/app/Transformers/Api/Application/AllocationTransformer.php index 82621c42f..6a1734185 100644 --- a/app/Transformers/Api/Application/AllocationTransformer.php +++ b/app/Transformers/Api/Application/AllocationTransformer.php @@ -2,12 +2,11 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Node; -use Pterodactyl\Models\Server; use Pterodactyl\Models\Allocation; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class AllocationTransformer extends BaseTransformer +class AllocationTransformer extends Transformer { /** * Relationships that can be loaded onto allocation transformations. @@ -16,24 +15,12 @@ class AllocationTransformer extends BaseTransformer */ protected $availableIncludes = ['node', 'server']; - /** - * Return the resource name for the JSONAPI output. - * - * @return string - */ public function getResourceName(): string { return Allocation::RESOURCE_NAME; } - /** - * Return a generic transformed allocation array. - * - * @param \Pterodactyl\Models\Allocation $model - * - * @return array - */ - public function transform(Allocation $model) + public function transform(Allocation $model): array { return [ 'id' => $model->id, @@ -41,47 +28,35 @@ class AllocationTransformer extends BaseTransformer 'alias' => $model->ip_alias, 'port' => $model->port, 'notes' => $model->notes, - 'assigned' => ! is_null($model->server_id), + 'assigned' => !is_null($model->server_id), ]; } /** * Load the node relationship onto a given transformation. * - * @param \Pterodactyl\Models\Allocation $allocation - * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function includeNode(Allocation $allocation) { - if (! $this->authorize(AdminAcl::RESOURCE_NODES)) { + if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { return $this->null(); } - return $this->item( - $allocation->node, $this->makeTransformer(NodeTransformer::class), Node::RESOURCE_NAME - ); + return $this->item($allocation->node, new NodeTransformer()); } /** * Load the server relationship onto a given transformation. * - * @param \Pterodactyl\Models\Allocation $allocation - * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function includeServer(Allocation $allocation) { - if (! $this->authorize(AdminAcl::RESOURCE_SERVERS) || ! $allocation->server) { + if (!$this->authorize(AdminAcl::RESOURCE_SERVERS) || !$allocation->server) { return $this->null(); } - return $this->item( - $allocation->server, $this->makeTransformer(ServerTransformer::class), Server::RESOURCE_NAME - ); + return $this->item($allocation->server, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/BaseTransformer.php b/app/Transformers/Api/Application/BaseTransformer.php deleted file mode 100644 index 3fd237d1a..000000000 --- a/app/Transformers/Api/Application/BaseTransformer.php +++ /dev/null @@ -1,125 +0,0 @@ -call([$this, 'handle']); - } - } - - /** - * Set the HTTP request class being used for this request. - * - * @return $this - */ - public function setKey(ApiKey $key) - { - $this->key = $key; - - return $this; - } - - /** - * Return the request instance being used for this transformer. - */ - public function getKey(): ApiKey - { - return $this->key; - } - - /** - * ? - * - * @return $this - */ - public function setRootAdmin(bool $rootAdmin) - { - $this->rootAdmin = $rootAdmin; - - return $this; - } - - /** - * ? - */ - public function isRootAdmin(): bool - { - return $this->rootAdmin; - } - - /** - * Determine if the API key loaded onto the transformer has permission - * to access a different resource. This is used when including other - * models on a transformation request. - */ - protected function authorize(string $resource): bool - { - if ($this->getKey()->key_type === ApiKey::TYPE_ACCOUNT && $this->isRootAdmin()) { - return true; - } - - return AdminAcl::check($this->getKey(), $resource, AdminAcl::READ); - } - - /** - * Create a new instance of the transformer and pass along the currently - * set API key. - * - * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - protected function makeTransformer(string $abstract, array $parameters = []) - { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = Container::getInstance()->makeWith($abstract, $parameters); - $transformer->setKey($this->getKey()); - - if (!$transformer instanceof self) { - throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); - } - - return $transformer; - } - - /** - * Return an ISO-8601 formatted timestamp to use in the API response. - */ - protected function formatTimestamp(string $timestamp): string - { - return CarbonImmutable::createFromFormat(CarbonImmutable::DEFAULT_TO_STRING_FORMAT, $timestamp) - ->setTimezone(self::RESPONSE_TIMEZONE) - ->toIso8601String(); - } -} diff --git a/app/Transformers/Api/Application/DatabaseHostTransformer.php b/app/Transformers/Api/Application/DatabaseHostTransformer.php index 4c9e86d44..dd45c3614 100644 --- a/app/Transformers/Api/Application/DatabaseHostTransformer.php +++ b/app/Transformers/Api/Application/DatabaseHostTransformer.php @@ -2,11 +2,11 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Database; use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class DatabaseHostTransformer extends BaseTransformer +class DatabaseHostTransformer extends Transformer { /** * @var array @@ -15,23 +15,12 @@ class DatabaseHostTransformer extends BaseTransformer 'databases', ]; - /** - * Return the resource name for the JSONAPI output. - * - * @return string - */ public function getResourceName(): string { return DatabaseHost::RESOURCE_NAME; } - /** - * Transform database host into a representation for the application API. - * - * @param \Pterodactyl\Models\DatabaseHost $model - * @return array - */ - public function transform(DatabaseHost $model) + public function transform(DatabaseHost $model): array { return [ 'id' => $model->id, @@ -40,8 +29,8 @@ class DatabaseHostTransformer extends BaseTransformer 'port' => $model->port, 'username' => $model->username, 'node' => $model->node_id, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } @@ -49,9 +38,6 @@ class DatabaseHostTransformer extends BaseTransformer * Include the databases associated with this host. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeDatabases(DatabaseHost $model) { @@ -59,8 +45,6 @@ class DatabaseHostTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('databases'); - - return $this->collection($model->getRelation('databases'), $this->makeTransformer(ServerDatabaseTransformer::class), Database::RESOURCE_NAME); + return $this->collection($model->databases, new ServerDatabaseTransformer()); } } diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index a1348a0ad..b6ebbc97d 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -3,12 +3,10 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Egg; -use Pterodactyl\Models\Nest; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\EggVariable; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class EggTransformer extends BaseTransformer +class EggTransformer extends Transformer { /** * Relationships that can be loaded onto this transformation. @@ -23,21 +21,12 @@ class EggTransformer extends BaseTransformer 'variables', ]; - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Egg::RESOURCE_NAME; } - /** - * Transform an Egg model into a representation that can be consumed by - * the application api. - * - * @return array - */ - public function transform(Egg $model) + public function transform(Egg $model): array { return [ 'id' => $model->id, @@ -67,8 +56,8 @@ class EggTransformer extends BaseTransformer 'container' => $model->script_container, 'extends' => $model->copy_script_from, ], - $model->getCreatedAtColumn() => $this->formatTimestamp($model->created_at), - $model->getUpdatedAtColumn() => $this->formatTimestamp($model->updated_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } @@ -76,8 +65,6 @@ class EggTransformer extends BaseTransformer * Include the Nest relationship for the given Egg in the transformation. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNest(Egg $model) { @@ -85,17 +72,13 @@ class EggTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('nest'); - - return $this->item($model->getRelation('nest'), $this->makeTransformer(NestTransformer::class), Nest::RESOURCE_NAME); + return $this->item($model->nest, new NestTransformer()); } /** * Include the Servers relationship for the given Egg in the transformation. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServers(Egg $model) { @@ -103,9 +86,7 @@ class EggTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('servers'); - - return $this->collection($model->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), Server::RESOURCE_NAME); + return $this->collection($model->servers, new ServerTransformer()); } /** @@ -120,8 +101,6 @@ class EggTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('configFrom'); - return $this->item($model, function (Egg $model) { return [ 'files' => json_decode($model->inherit_config_files), @@ -144,8 +123,6 @@ class EggTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('scriptFrom'); - return $this->item($model, function (Egg $model) { return [ 'privileged' => $model->script_is_privileged, @@ -160,8 +137,6 @@ class EggTransformer extends BaseTransformer * Include the variables that are defined for this Egg. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeVariables(Egg $model) { @@ -169,12 +144,6 @@ class EggTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('variables'); - - return $this->collection( - $model->getRelation('variables'), - $this->makeTransformer(EggVariableTransformer::class), - EggVariable::RESOURCE_NAME - ); + return $this->collection($model->variables, new EggVariableTransformer()); } } diff --git a/app/Transformers/Api/Application/EggVariableTransformer.php b/app/Transformers/Api/Application/EggVariableTransformer.php index 2088806d5..d255a633e 100644 --- a/app/Transformers/Api/Application/EggVariableTransformer.php +++ b/app/Transformers/Api/Application/EggVariableTransformer.php @@ -4,18 +4,16 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Egg; use Pterodactyl\Models\EggVariable; +use Pterodactyl\Transformers\Api\Transformer; -class EggVariableTransformer extends BaseTransformer +class EggVariableTransformer extends Transformer { - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Egg::RESOURCE_NAME; } - public function transform(EggVariable $model) + public function transform(EggVariable $model): array { return $model->toArray(); } diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php index eceb0e9e7..0e5c857c6 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -4,8 +4,9 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Location; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class LocationTransformer extends BaseTransformer +class LocationTransformer extends Transformer { /** * List of resources that can be included. @@ -14,25 +15,19 @@ class LocationTransformer extends BaseTransformer */ protected $availableIncludes = ['nodes', 'servers']; - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Location::RESOURCE_NAME; } - /** - * Return a generic transformed location array. - */ public function transform(Location $model): array { return [ 'id' => $model->id, 'short' => $model->short, 'long' => $model->long, - $model->getUpdatedAtColumn() => $this->formatTimestamp($model->updated_at), - $model->getCreatedAtColumn() => $this->formatTimestamp($model->created_at), + $model->getUpdatedAtColumn() => self::formatTimestamp($model->updated_at), + $model->getCreatedAtColumn() => self::formatTimestamp($model->created_at), ]; } @@ -40,9 +35,6 @@ class LocationTransformer extends BaseTransformer * Return the servers associated with this location. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServers(Location $location) { @@ -50,18 +42,13 @@ class LocationTransformer extends BaseTransformer return $this->null(); } - $location->loadMissing('servers'); - - return $this->collection($location->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), 'server'); + return $this->collection($location->servers, new ServerTransformer()); } /** * Return the nodes associated with this location. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNodes(Location $location) { @@ -69,8 +56,6 @@ class LocationTransformer extends BaseTransformer return $this->null(); } - $location->loadMissing('nodes'); - - return $this->collection($location->getRelation('nodes'), $this->makeTransformer(NodeTransformer::class), 'node'); + return $this->collection($location->nodes, new NodeTransformer()); } } diff --git a/app/Transformers/Api/Application/MountTransformer.php b/app/Transformers/Api/Application/MountTransformer.php index d192edf49..08a270913 100644 --- a/app/Transformers/Api/Application/MountTransformer.php +++ b/app/Transformers/Api/Application/MountTransformer.php @@ -2,13 +2,11 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Egg; -use Pterodactyl\Models\Node; use Pterodactyl\Models\Mount; -use Pterodactyl\Models\Server; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class MountTransformer extends BaseTransformer +class MountTransformer extends Transformer { /** * List of resources that can be included. @@ -17,22 +15,11 @@ class MountTransformer extends BaseTransformer */ protected $availableIncludes = ['eggs', 'nodes', 'servers']; - /** - * Return the resource name for the JSONAPI output. - * - * @return string - */ public function getResourceName(): string { return Mount::RESOURCE_NAME; } - /** - * Return a transformed Mount model that can be consumed by external services. - * - * @param \Pterodactyl\Models\Mount $model - * @return array - */ public function transform(Mount $model): array { return [ @@ -50,72 +37,42 @@ class MountTransformer extends BaseTransformer /** * Return the eggs associated with this mount. * - * @param \Pterodactyl\Models\Mount $mount - * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function includeEggs(Mount $mount) { - if (! $this->authorize(AdminAcl::RESOURCE_EGGS)) { + if (!$this->authorize(AdminAcl::RESOURCE_EGGS)) { return $this->null(); } - $mount->loadMissing('eggs'); - - return $this->collection( - $mount->getRelation('eggs'), - $this->makeTransformer(EggTransformer::class), - Egg::RESOURCE_NAME - ); + return $this->collection($mount->eggs, new EggTransformer()); } /** * Return the nodes associated with this mount. * - * @param \Pterodactyl\Models\Mount $mount - * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function includeNodes(Mount $mount) { - if (! $this->authorize(AdminAcl::RESOURCE_NODES)) { + if (!$this->authorize(AdminAcl::RESOURCE_NODES)) { return $this->null(); } - $mount->loadMissing('nodes'); - - return $this->collection( - $mount->getRelation('nodes'), - $this->makeTransformer(NodeTransformer::class), - Node::RESOURCE_NAME - ); + return $this->collection($mount->nodes, new NodeTransformer()); } /** * Return the servers associated with this mount. * - * @param \Pterodactyl\Models\Mount $mount - * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function includeServers(Mount $mount) { - if (! $this->authorize(AdminAcl::RESOURCE_SERVERS)) { + if (!$this->authorize(AdminAcl::RESOURCE_SERVERS)) { return $this->null(); } - $mount->loadMissing('servers'); - - return $this->collection( - $mount->getRelation('servers'), - $this->makeTransformer(ServerTransformer::class), - Server::RESOURCE_NAME - ); + return $this->collection($mount->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/NestTransformer.php b/app/Transformers/Api/Application/NestTransformer.php index 506b0276b..160e4474d 100644 --- a/app/Transformers/Api/Application/NestTransformer.php +++ b/app/Transformers/Api/Application/NestTransformer.php @@ -2,12 +2,11 @@ namespace Pterodactyl\Transformers\Api\Application; -use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; -use Pterodactyl\Models\Server; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class NestTransformer extends BaseTransformer +class NestTransformer extends Transformer { /** * Relationships that can be loaded onto this transformation. @@ -15,29 +14,21 @@ class NestTransformer extends BaseTransformer * @var array */ protected $availableIncludes = [ - 'eggs', 'servers', + 'eggs', + 'servers', ]; - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Nest::RESOURCE_NAME; } - /** - * Transform a Nest model into a representation that can be consumed by the - * application API. - * - * @return array - */ - public function transform(Nest $model) + public function transform(Nest $model): array { $response = $model->toArray(); - $response[$model->getUpdatedAtColumn()] = $this->formatTimestamp($model->updated_at); - $response[$model->getCreatedAtColumn()] = $this->formatTimestamp($model->created_at); + $response[$model->getUpdatedAtColumn()] = self::formatTimestamp($model->updated_at); + $response[$model->getCreatedAtColumn()] = self::formatTimestamp($model->created_at); return $response; } @@ -46,8 +37,6 @@ class NestTransformer extends BaseTransformer * Include the Eggs relationship on the given Nest model transformation. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEggs(Nest $model) { @@ -55,17 +44,13 @@ class NestTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('eggs'); - - return $this->collection($model->getRelation('eggs'), $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); + return $this->collection($model->eggs, new EggTransformer()); } /** * Include the servers relationship on the given Nest model. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServers(Nest $model) { @@ -73,8 +58,6 @@ class NestTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('servers'); - - return $this->collection($model->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), Server::RESOURCE_NAME); + return $this->collection($model->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/NodeTransformer.php b/app/Transformers/Api/Application/NodeTransformer.php index d7de33ce6..c0f3b756f 100644 --- a/app/Transformers/Api/Application/NodeTransformer.php +++ b/app/Transformers/Api/Application/NodeTransformer.php @@ -3,10 +3,10 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Node; -use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class NodeTransformer extends BaseTransformer +class NodeTransformer extends Transformer { /** * List of resources that can be included. @@ -15,24 +15,17 @@ class NodeTransformer extends BaseTransformer */ protected $availableIncludes = ['allocations', 'database_host', 'location', 'mounts', 'servers']; - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Node::RESOURCE_NAME; } - /** - * Return a node transformed into a format that can be consumed by the - * external administrative API. - */ public function transform(Node $model): array { $response = $model->toArray(); - $response[$model->getUpdatedAtColumn()] = $this->formatTimestamp($model->updated_at); - $response[$model->getCreatedAtColumn()] = $this->formatTimestamp($model->created_at); + $response[$model->getUpdatedAtColumn()] = self::formatTimestamp($model->updated_at); + $response[$model->getCreatedAtColumn()] = self::formatTimestamp($model->created_at); $resources = $model->servers()->select(['memory', 'disk'])->get(); @@ -48,9 +41,6 @@ class NodeTransformer extends BaseTransformer * Return the allocations associated with this node. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeAllocations(Node $node) { @@ -58,50 +48,27 @@ class NodeTransformer extends BaseTransformer return $this->null(); } - $node->loadMissing('allocations'); - - return $this->collection( - $node->getRelation('allocations'), - $this->makeTransformer(AllocationTransformer::class), - 'allocation' - ); + return $this->collection($node->allocations, new AllocationTransformer()); } /** * Return the database host associated with this node. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeDatabaseHost(Node $node) { - if (!$this->authorize(AdminAcl::RESOURCE_DATABASE_HOSTS)) { + if (!$this->authorize(AdminAcl::RESOURCE_DATABASE_HOSTS) || is_null($node->databaseHost)) { return $this->null(); } - $node->loadMissing('databaseHost'); - - $databaseHost = $node->getRelation('databaseHost'); - if (is_null($databaseHost)) { - return new NullResource(); - } - - return $this->item( - $node->getRelation('databaseHost'), - $this->makeTransformer(DatabaseHostTransformer::class), - 'databaseHost' - ); + return $this->item($node->databaseHost, new DatabaseHostTransformer()); } /** * Return the location associated with this node. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeLocation(Node $node) { @@ -109,22 +76,13 @@ class NodeTransformer extends BaseTransformer return $this->null(); } - $node->loadMissing('location'); - - return $this->item( - $node->getRelation('location'), - $this->makeTransformer(LocationTransformer::class), - 'location' - ); + return $this->item($node->location, new LocationTransformer()); } /** * Return the mounts associated with this node. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeMounts(Node $node) { @@ -132,22 +90,13 @@ class NodeTransformer extends BaseTransformer return $this->null(); } - $node->loadMissing('mounts'); - - return $this->collection( - $node->getRelation('mounts'), - $this->makeTransformer(MountTransformer::class), - 'mount' - ); + return $this->collection($node->mounts, new MountTransformer()); } /** * Return the servers associated with this node. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServers(Node $node) { @@ -155,12 +104,6 @@ class NodeTransformer extends BaseTransformer return $this->null(); } - $node->loadMissing('servers'); - - return $this->collection( - $node->getRelation('servers'), - $this->makeTransformer(ServerTransformer::class), - 'server' - ); + return $this->collection($node->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/ServerDatabaseTransformer.php b/app/Transformers/Api/Application/ServerDatabaseTransformer.php index 95180e2d9..66b9bd522 100644 --- a/app/Transformers/Api/Application/ServerDatabaseTransformer.php +++ b/app/Transformers/Api/Application/ServerDatabaseTransformer.php @@ -3,41 +3,29 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Database; -use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; use Illuminate\Contracts\Encryption\Encrypter; -class ServerDatabaseTransformer extends BaseTransformer +class ServerDatabaseTransformer extends Transformer { /** * @var array */ protected $availableIncludes = ['password', 'host']; - /** - * @var Encrypter - */ - private $encrypter; + protected Encrypter $encrypter; - /** - * Perform dependency injection. - */ public function handle(Encrypter $encrypter) { $this->encrypter = $encrypter; } - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Database::RESOURCE_NAME; } - /** - * Transform a database model in a representation for the application API. - */ public function transform(Database $model): array { return [ @@ -48,8 +36,8 @@ class ServerDatabaseTransformer extends BaseTransformer 'username' => $model->username, 'remote' => $model->remote, 'max_connections' => $model->max_connections, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } @@ -71,8 +59,6 @@ class ServerDatabaseTransformer extends BaseTransformer * Return the database host relationship for this server database. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeHost(Database $model) { @@ -80,12 +66,6 @@ class ServerDatabaseTransformer extends BaseTransformer return $this->null(); } - $model->loadMissing('host'); - - return $this->item( - $model->getRelation('host'), - $this->makeTransformer(DatabaseHostTransformer::class), - DatabaseHost::RESOURCE_NAME - ); + return $this->item($model->host, new DatabaseHostTransformer()); } } diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index 47b1e6409..fb2c7ceda 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -4,14 +4,12 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Server; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Services\Servers\EnvironmentService; -class ServerTransformer extends BaseTransformer +class ServerTransformer extends Transformer { - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - private $environmentService; + protected EnvironmentService $environmentService; /** * List of resources that can be included. @@ -31,25 +29,16 @@ class ServerTransformer extends BaseTransformer 'transfer', ]; - /** - * Perform dependency injection. - */ public function handle(EnvironmentService $environmentService) { $this->environmentService = $environmentService; } - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Server::RESOURCE_NAME; } - /** - * Return a generic transformed server array. - */ public function transform(Server $model): array { return [ @@ -83,8 +72,8 @@ class ServerTransformer extends BaseTransformer 'image' => $model->image, 'environment' => $this->environmentService->handle($model), ], - $model->getUpdatedAtColumn() => $this->formatTimestamp($model->updated_at), - $model->getCreatedAtColumn() => $this->formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), + 'created_at' => self::formatTimestamp($model->created_at), ]; } @@ -92,8 +81,6 @@ class ServerTransformer extends BaseTransformer * Return a generic array of allocations for this server. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeAllocations(Server $server) { @@ -101,17 +88,13 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('allocations'); - - return $this->collection($server->getRelation('allocations'), $this->makeTransformer(AllocationTransformer::class), 'allocation'); + return $this->collection($server->allocations, new AllocationTransformer()); } /** * Return a generic array of data about subusers for this server. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeSubusers(Server $server) { @@ -119,17 +102,13 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('subusers'); - - return $this->collection($server->getRelation('subusers'), $this->makeTransformer(SubuserTransformer::class), 'subuser'); + return $this->collection($server->subusers, new SubuserTransformer()); } /** * Return a generic array of data about subusers for this server. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeUser(Server $server) { @@ -137,17 +116,13 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('user'); - - return $this->item($server->getRelation('user'), $this->makeTransformer(UserTransformer::class), 'user'); + return $this->item($server->user, new UserTransformer()); } /** * Return a generic array with nest information for this server. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNest(Server $server) { @@ -155,17 +130,13 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('nest'); - - return $this->item($server->getRelation('nest'), $this->makeTransformer(NestTransformer::class), 'nest'); + return $this->item($server->nest, new NestTransformer()); } /** * Return a generic array with egg information for this server. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEgg(Server $server) { @@ -173,17 +144,13 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('egg'); - - return $this->item($server->getRelation('egg'), $this->makeTransformer(EggTransformer::class), 'egg'); + return $this->item($server->egg, new EggTransformer()); } /** * Return a generic array of data about subusers for this server. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeVariables(Server $server) { @@ -191,17 +158,13 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('variables'); - - return $this->collection($server->getRelation('variables'), $this->makeTransformer(ServerVariableTransformer::class), 'server_variable'); + return $this->collection($server->variables, new ServerVariableTransformer()); } /** * Return a generic array with location information for this server. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeLocation(Server $server) { @@ -209,17 +172,13 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('location'); - - return $this->item($server->getRelation('location'), $this->makeTransformer(LocationTransformer::class), 'location'); + return $this->item($server->location, new LocationTransformer()); } /** * Return a generic array with node information for this server. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeNode(Server $server) { @@ -227,17 +186,13 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('node'); - - return $this->item($server->getRelation('node'), $this->makeTransformer(NodeTransformer::class), 'node'); + return $this->item($server->node, new NodeTransformer()); } /** * Return a generic array with database information for this server. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeDatabases(Server $server) { @@ -245,8 +200,6 @@ class ServerTransformer extends BaseTransformer return $this->null(); } - $server->loadMissing('databases'); - - return $this->collection($server->getRelation('databases'), $this->makeTransformer(ServerDatabaseTransformer::class), 'databases'); + return $this->collection($server->databases, new ServerDatabaseTransformer()); } } diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index aefa318e2..af06b1e97 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -3,9 +3,11 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\EggVariable; +use Pterodactyl\Models\ServerVariable; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class ServerVariableTransformer extends BaseTransformer +class ServerVariableTransformer extends Transformer { /** * List of resources that can be included. @@ -22,12 +24,7 @@ class ServerVariableTransformer extends BaseTransformer return ServerVariable::RESOURCE_NAME; } - /** - * Return a generic transformed server variable array. - * - * @return array - */ - public function transform(EggVariable $variable) + public function transform(EggVariable $variable): array { return $variable->toArray(); } @@ -36,8 +33,6 @@ class ServerVariableTransformer extends BaseTransformer * Return the parent service variable data. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeParent(EggVariable $variable) { @@ -45,8 +40,6 @@ class ServerVariableTransformer extends BaseTransformer return $this->null(); } - $variable->loadMissing('variable'); - - return $this->item($variable->getRelation('variable'), $this->makeTransformer(EggVariableTransformer::class), 'variable'); + return $this->item($variable->variable, new EggVariableTransformer()); } } diff --git a/app/Transformers/Api/Application/SubuserTransformer.php b/app/Transformers/Api/Application/SubuserTransformer.php index 08c622666..41c725d1f 100644 --- a/app/Transformers/Api/Application/SubuserTransformer.php +++ b/app/Transformers/Api/Application/SubuserTransformer.php @@ -4,8 +4,9 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\Subuser; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class SubuserTransformer extends BaseTransformer +class SubuserTransformer extends Transformer { /** * List of resources that can be included. @@ -14,17 +15,11 @@ class SubuserTransformer extends BaseTransformer */ protected $availableIncludes = ['user', 'server']; - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Subuser::RESOURCE_NAME; } - /** - * Return a transformed Subuser model that can be consumed by external services. - */ public function transform(Subuser $subuser): array { return [ @@ -32,8 +27,8 @@ class SubuserTransformer extends BaseTransformer 'user_id' => $subuser->user_id, 'server_id' => $subuser->server_id, 'permissions' => $subuser->permissions, - 'created_at' => $this->formatTimestamp($subuser->created_at), - 'updated_at' => $this->formatTimestamp($subuser->updated_at), + 'created_at' => self::formatTimestamp($subuser->created_at), + 'updated_at' => self::formatTimestamp($subuser->updated_at), ]; } @@ -41,8 +36,6 @@ class SubuserTransformer extends BaseTransformer * Return a generic item of user for this subuser. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeUser(Subuser $subuser) { @@ -50,17 +43,13 @@ class SubuserTransformer extends BaseTransformer return $this->null(); } - $subuser->loadMissing('user'); - - return $this->item($subuser->getRelation('user'), $this->makeTransformer(UserTransformer::class), 'user'); + return $this->item($subuser->user, new UserTransformer()); } /** * Return a generic item of server for this subuser. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServer(Subuser $subuser) { @@ -68,8 +57,6 @@ class SubuserTransformer extends BaseTransformer return $this->null(); } - $subuser->loadMissing('server'); - - return $this->item($subuser->getRelation('server'), $this->makeTransformer(ServerTransformer::class), 'server'); + return $this->item($subuser->server, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Application/UserTransformer.php b/app/Transformers/Api/Application/UserTransformer.php index f79aad102..d0439e654 100644 --- a/app/Transformers/Api/Application/UserTransformer.php +++ b/app/Transformers/Api/Application/UserTransformer.php @@ -4,8 +4,9 @@ namespace Pterodactyl\Transformers\Api\Application; use Pterodactyl\Models\User; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Transformers\Api\Transformer; -class UserTransformer extends BaseTransformer +class UserTransformer extends Transformer { /** * List of resources that can be included. @@ -14,17 +15,11 @@ class UserTransformer extends BaseTransformer */ protected $availableIncludes = ['role', 'servers']; - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return User::RESOURCE_NAME; } - /** - * Return a transformed User model that can be consumed by external services. - */ public function transform(User $model): array { return [ @@ -39,8 +34,8 @@ class UserTransformer extends BaseTransformer 'avatar_url' => $model->avatarURL(), 'admin_role_id' => $model->admin_role_id, 'role_name' => $model->adminRoleName(), - 'created_at' => $this->formatTimestamp($model->created_at), - 'updated_at' => $this->formatTimestamp($model->updated_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } @@ -48,9 +43,6 @@ class UserTransformer extends BaseTransformer * Return the role associated with this user. * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeRole(User $user) { @@ -58,18 +50,13 @@ class UserTransformer extends BaseTransformer return $this->null(); } - $user->loadMissing('adminRole'); - - return $this->item($user->getRelation('adminRole'), $this->makeTransformer(AdminRoleTransformer::class), 'admin_role'); + return $this->item($user->adminRole, new AdminRoleTransformer()); } /** * Return the servers associated with this user. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeServers(User $user) { @@ -77,8 +64,6 @@ class UserTransformer extends BaseTransformer return $this->null(); } - $user->loadMissing('servers'); - - return $this->collection($user->getRelation('servers'), $this->makeTransformer(ServerTransformer::class), 'server'); + return $this->collection($user->servers, new ServerTransformer()); } } diff --git a/app/Transformers/Api/Client/AccountTransformer.php b/app/Transformers/Api/Client/AccountTransformer.php index 770d24c02..6345593b2 100644 --- a/app/Transformers/Api/Client/AccountTransformer.php +++ b/app/Transformers/Api/Client/AccountTransformer.php @@ -3,23 +3,19 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\User; +use Pterodactyl\Transformers\Api\Transformer; -class AccountTransformer extends BaseClientTransformer +class AccountTransformer extends Transformer { - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { - return 'user'; + return User::RESOURCE_NAME; } /** * Return basic information about the currently logged in user. - * - * @return array */ - public function transform(User $model) + public function transform(User $model): array { return [ 'id' => $model->id, diff --git a/app/Transformers/Api/Client/AllocationTransformer.php b/app/Transformers/Api/Client/AllocationTransformer.php index 0d9bfec7c..4379e2ced 100644 --- a/app/Transformers/Api/Client/AllocationTransformer.php +++ b/app/Transformers/Api/Client/AllocationTransformer.php @@ -3,23 +3,16 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Allocation; +use Pterodactyl\Transformers\Api\Transformer; -class AllocationTransformer extends BaseClientTransformer +class AllocationTransformer extends Transformer { - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { - return 'allocation'; + return Allocation::RESOURCE_NAME; } - /** - * Return basic information about the currently logged in user. - * - * @return array - */ - public function transform(Allocation $model) + public function transform(Allocation $model): array { return [ 'id' => $model->id, diff --git a/app/Transformers/Api/Client/ApiKeyTransformer.php b/app/Transformers/Api/Client/ApiKeyTransformer.php deleted file mode 100644 index c7c39c2b4..000000000 --- a/app/Transformers/Api/Client/ApiKeyTransformer.php +++ /dev/null @@ -1,32 +0,0 @@ - $model->identifier, - 'description' => $model->memo, - 'allowed_ips' => $model->allowed_ips, - 'last_used_at' => $model->last_used_at ? $model->last_used_at->toIso8601String() : null, - 'created_at' => $model->created_at->toIso8601String(), - ]; - } -} diff --git a/app/Transformers/Api/Client/BackupTransformer.php b/app/Transformers/Api/Client/BackupTransformer.php index ae6ae5213..fe6a27291 100644 --- a/app/Transformers/Api/Client/BackupTransformer.php +++ b/app/Transformers/Api/Client/BackupTransformer.php @@ -3,18 +3,16 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Backup; +use Pterodactyl\Transformers\Api\Transformer; -class BackupTransformer extends BaseClientTransformer +class BackupTransformer extends Transformer { public function getResourceName(): string { return Backup::RESOURCE_NAME; } - /** - * @return array - */ - public function transform(Backup $backup) + public function transform(Backup $backup): array { return [ 'uuid' => $backup->uuid, @@ -24,8 +22,8 @@ class BackupTransformer extends BaseClientTransformer 'ignored_files' => $backup->ignored_files, 'checksum' => $backup->checksum, 'bytes' => $backup->bytes, - 'created_at' => $backup->created_at->toIso8601String(), - 'completed_at' => $backup->completed_at ? $backup->completed_at->toIso8601String() : null, + 'created_at' => self::formatTimestamp($backup->created_at), + 'completed_at' => self::formatTimestamp($backup->completed_at), ]; } } diff --git a/app/Transformers/Api/Client/BaseClientTransformer.php b/app/Transformers/Api/Client/BaseClientTransformer.php deleted file mode 100644 index efe53276f..000000000 --- a/app/Transformers/Api/Client/BaseClientTransformer.php +++ /dev/null @@ -1,69 +0,0 @@ -user; - } - - /** - * Set the user model of the user requesting this transformation. - */ - public function setUser(User $user) - { - $this->user = $user; - } - - /** - * Determine if the API key loaded onto the transformer has permission - * to access a different resource. This is used when including other - * models on a transformation request. - * - * @param \Pterodactyl\Models\Server $server - */ - protected function authorize(string $ability, Server $server = null): bool - { - Assert::isInstanceOf($server, Server::class); - - return $this->getUser()->can($ability, [$server]); - } - - /** - * Create a new instance of the transformer and pass along the currently - * set API key. - * - * @return self - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - protected function makeTransformer(string $abstract, array $parameters = []) - { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = Container::getInstance()->makeWith($abstract, $parameters); - $transformer->setKey($this->getKey()); - - if (!$transformer instanceof self) { - throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); - } - - return $transformer; - } -} diff --git a/app/Transformers/Api/Client/DatabaseTransformer.php b/app/Transformers/Api/Client/DatabaseTransformer.php index 9c5bd86d4..36b7c1720 100644 --- a/app/Transformers/Api/Client/DatabaseTransformer.php +++ b/app/Transformers/Api/Client/DatabaseTransformer.php @@ -4,26 +4,18 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Database; use Pterodactyl\Models\Permission; +use Pterodactyl\Transformers\Api\Transformer; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Extensions\HashidsInterface; -class DatabaseTransformer extends BaseClientTransformer +class DatabaseTransformer extends Transformer { protected $availableIncludes = ['password']; - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; + protected Encrypter $encrypter; - /** - * @var \Pterodactyl\Contracts\Extensions\HashidsInterface - */ - private $hashids; + protected HashidsInterface $hashids; - /** - * Handle dependency injection. - */ public function handle(Encrypter $encrypter, HashidsInterface $hashids) { $this->encrypter = $encrypter; @@ -37,13 +29,11 @@ class DatabaseTransformer extends BaseClientTransformer public function transform(Database $model): array { - $model->loadMissing('host'); - return [ 'id' => $this->hashids->encode($model->id), 'host' => [ - 'address' => $model->getRelation('host')->host, - 'port' => $model->getRelation('host')->port, + 'address' => $model->host->host, + 'port' => $model->host->port, ], 'name' => $model->database, 'username' => $model->username, @@ -59,7 +49,7 @@ class DatabaseTransformer extends BaseClientTransformer */ public function includePassword(Database $database) { - if (!$this->getUser()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { + if ($this->user()->cannot(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) { return $this->null(); } diff --git a/app/Transformers/Api/Client/EggTransformer.php b/app/Transformers/Api/Client/EggTransformer.php index 25e135007..46f73d13b 100644 --- a/app/Transformers/Api/Client/EggTransformer.php +++ b/app/Transformers/Api/Client/EggTransformer.php @@ -3,21 +3,16 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Egg; +use Pterodactyl\Transformers\Api\Transformer; -class EggTransformer extends BaseClientTransformer +class EggTransformer extends Transformer { - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Egg::RESOURCE_NAME; } - /** - * @return array - */ - public function transform(Egg $egg) + public function transform(Egg $egg): array { return [ 'uuid' => $egg->uuid, diff --git a/app/Transformers/Api/Client/EggVariableTransformer.php b/app/Transformers/Api/Client/EggVariableTransformer.php index c7725b978..f37153e7a 100644 --- a/app/Transformers/Api/Client/EggVariableTransformer.php +++ b/app/Transformers/Api/Client/EggVariableTransformer.php @@ -4,18 +4,16 @@ namespace Pterodactyl\Transformers\Api\Client; use BadMethodCallException; use Pterodactyl\Models\EggVariable; +use Pterodactyl\Transformers\Api\Transformer; -class EggVariableTransformer extends BaseClientTransformer +class EggVariableTransformer extends Transformer { public function getResourceName(): string { return EggVariable::RESOURCE_NAME; } - /** - * @return array - */ - public function transform(EggVariable $variable) + public function transform(EggVariable $variable): array { // This guards against someone incorrectly retrieving variables (haha, me) and then passing // them into the transformer and along to the user. Just throw an exception and break the entire diff --git a/app/Transformers/Api/Client/PersonalAccessTokenTransformer.php b/app/Transformers/Api/Client/PersonalAccessTokenTransformer.php new file mode 100644 index 000000000..d0037b2cd --- /dev/null +++ b/app/Transformers/Api/Client/PersonalAccessTokenTransformer.php @@ -0,0 +1,26 @@ + $model->token_id, + 'description' => $model->description, + 'abilities' => $model->abilities ?? [], + 'last_used_at' => self::formatTimestamp($model->last_used_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), + ]; + } +} diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php index 25ef9fb58..1700c14f7 100644 --- a/app/Transformers/Api/Client/ScheduleTransformer.php +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -2,11 +2,10 @@ namespace Pterodactyl\Transformers\Api\Client; -use Pterodactyl\Models\Task; use Pterodactyl\Models\Schedule; -use Illuminate\Database\Eloquent\Model; +use Pterodactyl\Transformers\Api\Transformer; -class ScheduleTransformer extends BaseClientTransformer +class ScheduleTransformer extends Transformer { /** * @var array @@ -18,20 +17,12 @@ class ScheduleTransformer extends BaseClientTransformer */ protected $defaultIncludes = ['tasks']; - /** - * {@inheritdoc} - */ public function getResourceName(): string { return Schedule::RESOURCE_NAME; } - /** - * Returns a transformed schedule model such that a client can view the information. - * - * @return array - */ - public function transform(Schedule $model) + public function transform(Schedule $model): array { return [ 'id' => $model->id, @@ -46,10 +37,10 @@ class ScheduleTransformer extends BaseClientTransformer 'is_active' => $model->is_active, 'is_processing' => $model->is_processing, 'only_when_online' => $model->only_when_online, - 'last_run_at' => $model->last_run_at ? $model->last_run_at->toIso8601String() : null, - 'next_run_at' => $model->next_run_at ? $model->next_run_at->toIso8601String() : null, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'last_run_at' => self::formatTimestamp($model->last_run_at), + 'next_run_at' => self::formatTimestamp($model->next_run_at), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } @@ -57,15 +48,9 @@ class ScheduleTransformer extends BaseClientTransformer * Allows attaching the tasks specific to the schedule in the response. * * @return \League\Fractal\Resource\Collection - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeTasks(Schedule $model) { - return $this->collection( - $model->tasks, - $this->makeTransformer(TaskTransformer::class), - Task::RESOURCE_NAME - ); + return $this->collection($model->tasks, new TaskTransformer()); } } diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php index ed10c76d5..cd9385742 100644 --- a/app/Transformers/Api/Client/ServerTransformer.php +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -2,16 +2,12 @@ namespace Pterodactyl\Transformers\Api\Client; -use Pterodactyl\Models\Egg; use Pterodactyl\Models\Server; -use Pterodactyl\Models\Subuser; -use Pterodactyl\Models\Allocation; use Pterodactyl\Models\Permission; -use Illuminate\Container\Container; -use Pterodactyl\Models\EggVariable; +use Pterodactyl\Transformers\Api\Transformer; use Pterodactyl\Services\Servers\StartupCommandService; -class ServerTransformer extends BaseClientTransformer +class ServerTransformer extends Transformer { /** * @var string[] @@ -23,22 +19,22 @@ class ServerTransformer extends BaseClientTransformer */ protected $availableIncludes = ['egg', 'subusers']; + protected StartupCommandService $service; + public function getResourceName(): string { return Server::RESOURCE_NAME; } - /** - * Transform a server model into a representation that can be returned - * to a client. - */ + public function handle(StartupCommandService $service) + { + $this->service = $service; + } + public function transform(Server $server): array { - /** @var \Pterodactyl\Services\Servers\StartupCommandService $service */ - $service = Container::getInstance()->make(StartupCommandService::class); - return [ - 'server_owner' => $this->getKey()->user_id === $server->owner_id, + 'server_owner' => $this->user()->id === $server->owner_id, 'identifier' => $server->uuidShort, 'internal_id' => $server->id, 'uuid' => $server->uuid, @@ -56,7 +52,7 @@ class ServerTransformer extends BaseClientTransformer 'io' => $server->io, 'cpu' => $server->cpu, ], - 'invocation' => $service->handle($server, !$this->getUser()->can(Permission::ACTION_STARTUP_READ, $server)), + 'invocation' => $this->service->handle($server, $this->user()->cannot(Permission::ACTION_STARTUP_READ, $server)), 'docker_image' => $server->image, 'egg_features' => $server->egg->inherit_features, 'feature_limits' => [ @@ -65,10 +61,6 @@ class ServerTransformer extends BaseClientTransformer 'backups' => $server->backup_limit, ], 'status' => $server->status, - // This field is deprecated, please use "status". - 'is_suspended' => $server->isSuspended(), - // This field is deprecated, please use "status". - 'is_installing' => !$server->isInstalled(), 'is_transferring' => !is_null($server->transfer), ]; } @@ -76,14 +68,10 @@ class ServerTransformer extends BaseClientTransformer /** * Returns the allocations associated with this server. * - * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException + * @return \League\Fractal\Resource\Collection */ public function includeAllocations(Server $server) { - $transformer = $this->makeTransformer(AllocationTransformer::class); - // While we include this permission, we do need to actually handle it slightly different here // for the purpose of keeping things functionally working. If the user doesn't have read permissions // for the allocations we'll only return the primary server allocation, and any notes associated @@ -91,59 +79,49 @@ class ServerTransformer extends BaseClientTransformer // // This allows us to avoid too much permission regression, without also hiding information that // is generally needed for the frontend to make sense when browsing or searching results. - if (!$this->getUser()->can(Permission::ACTION_ALLOCATION_READ, $server)) { + if ($this->user()->cannot(Permission::ACTION_ALLOCATION_READ, $server)) { $primary = clone $server->allocation; $primary->notes = null; - return $this->collection([$primary], $transformer, Allocation::RESOURCE_NAME); + return $this->collection([$primary], new AllocationTransformer()); } - return $this->collection($server->allocations, $transformer, Allocation::RESOURCE_NAME); + return $this->collection($server->allocations, new AllocationTransformer()); } /** * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeVariables(Server $server) { - if (!$this->getUser()->can(Permission::ACTION_STARTUP_READ, $server)) { + if ($this->user()->cannot(Permission::ACTION_STARTUP_READ, $server)) { return $this->null(); } - return $this->collection( - $server->variables->where('user_viewable', true), - $this->makeTransformer(EggVariableTransformer::class), - EggVariable::RESOURCE_NAME - ); + return $this->collection($server->variables->where('user_viewable', true), new EggVariableTransformer()); } /** * Returns the egg associated with this server. * * @return \League\Fractal\Resource\Item - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeEgg(Server $server) { - return $this->item($server->egg, $this->makeTransformer(EggTransformer::class), Egg::RESOURCE_NAME); + return $this->item($server->egg, new EggTransformer()); } /** * Returns the subusers associated with this server. * * @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function includeSubusers(Server $server) { - if (!$this->getUser()->can(Permission::ACTION_USER_READ, $server)) { + if ($this->user()->cannot(Permission::ACTION_USER_READ, $server)) { return $this->null(); } - return $this->collection($server->subusers, $this->makeTransformer(SubuserTransformer::class), Subuser::RESOURCE_NAME); + return $this->collection($server->subusers, new SubuserTransformer()); } } diff --git a/app/Transformers/Api/Client/StatsTransformer.php b/app/Transformers/Api/Client/StatsTransformer.php index bbe5afe86..b438a8e1f 100644 --- a/app/Transformers/Api/Client/StatsTransformer.php +++ b/app/Transformers/Api/Client/StatsTransformer.php @@ -3,21 +3,16 @@ namespace Pterodactyl\Transformers\Api\Client; use Illuminate\Support\Arr; +use Pterodactyl\Transformers\Api\Transformer; -class StatsTransformer extends BaseClientTransformer +class StatsTransformer extends Transformer { public function getResourceName(): string { return 'stats'; } - /** - * Transform stats from the daemon into a result set that can be used in - * the client API. - * - * @return array - */ - public function transform(array $data) + public function transform(array $data): array { return [ 'current_state' => Arr::get($data, 'state', 'stopped'), diff --git a/app/Transformers/Api/Client/SubuserTransformer.php b/app/Transformers/Api/Client/SubuserTransformer.php index 7a8bfde0e..1cde3741c 100644 --- a/app/Transformers/Api/Client/SubuserTransformer.php +++ b/app/Transformers/Api/Client/SubuserTransformer.php @@ -3,28 +3,19 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Subuser; +use Pterodactyl\Transformers\Api\Transformer; -class SubuserTransformer extends BaseClientTransformer +class SubuserTransformer extends Transformer { - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return Subuser::RESOURCE_NAME; } - /** - * Transforms a subuser into a model that can be shown to a front-end user. - * - * @return array|void - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException - */ - public function transform(Subuser $model) + public function transform(Subuser $model): array { return array_merge( - $this->makeTransformer(UserTransformer::class)->transform($model->user), + (new UserTransformer())->transform($model->user), ['permissions' => $model->permissions] ); } diff --git a/app/Transformers/Api/Client/TaskTransformer.php b/app/Transformers/Api/Client/TaskTransformer.php index a2e62cf51..94850072f 100644 --- a/app/Transformers/Api/Client/TaskTransformer.php +++ b/app/Transformers/Api/Client/TaskTransformer.php @@ -3,23 +3,16 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\Task; +use Pterodactyl\Transformers\Api\Transformer; -class TaskTransformer extends BaseClientTransformer +class TaskTransformer extends Transformer { - /** - * {@inheritdoc} - */ public function getResourceName(): string { return Task::RESOURCE_NAME; } - /** - * Transforms a schedule's task into a client viewable format. - * - * @return array - */ - public function transform(Task $model) + public function transform(Task $model): array { return [ 'id' => $model->id, @@ -29,8 +22,8 @@ class TaskTransformer extends BaseClientTransformer 'time_offset' => $model->time_offset, 'is_queued' => $model->is_queued, 'continue_on_failure' => $model->continue_on_failure, - 'created_at' => $model->created_at->toIso8601String(), - 'updated_at' => $model->updated_at->toIso8601String(), + 'created_at' => self::formatTimestamp($model->created_at), + 'updated_at' => self::formatTimestamp($model->updated_at), ]; } } diff --git a/app/Transformers/Api/Client/UserSSHKeyTransformer.php b/app/Transformers/Api/Client/UserSSHKeyTransformer.php index e137ccd51..57cbd3323 100644 --- a/app/Transformers/Api/Client/UserSSHKeyTransformer.php +++ b/app/Transformers/Api/Client/UserSSHKeyTransformer.php @@ -3,27 +3,25 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\UserSSHKey; +use Pterodactyl\Transformers\Api\Transformer; -class UserSSHKeyTransformer extends BaseClientTransformer +class UserSSHKeyTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. */ public function getResourceName(): string { - return 'user_ssh_key'; + return UserSSHKey::RESOURCE_NAME; } - /** - * Return basic information about the currently logged in user. - */ public function transform(UserSSHKey $model): array { return [ 'id' => $model->id, 'name' => $model->name, 'public_key' => $model->public_key, - 'created_at' => $model->created_at->toIso8601String(), + 'created_at' => self::formatTimestamp($model->created_at), ]; } } diff --git a/app/Transformers/Api/Client/UserTransformer.php b/app/Transformers/Api/Client/UserTransformer.php index 468232f8d..79f7f7c06 100644 --- a/app/Transformers/Api/Client/UserTransformer.php +++ b/app/Transformers/Api/Client/UserTransformer.php @@ -4,24 +4,16 @@ namespace Pterodactyl\Transformers\Api\Client; use Illuminate\Support\Str; use Pterodactyl\Models\User; +use Pterodactyl\Transformers\Api\Transformer; -class UserTransformer extends BaseClientTransformer +class UserTransformer extends Transformer { - /** - * Return the resource name for the JSONAPI output. - */ public function getResourceName(): string { return User::RESOURCE_NAME; } - /** - * Transforms a User model into a representation that can be shown to regular - * users of the API. - * - * @return array - */ - public function transform(User $model) + public function transform(User $model): array { return [ 'uuid' => $model->uuid, @@ -29,7 +21,7 @@ class UserTransformer extends BaseClientTransformer 'email' => $model->email, 'image' => 'https://gravatar.com/avatar/' . md5(Str::lower($model->email)), '2fa_enabled' => $model->use_totp, - 'created_at' => $model->created_at->toIso8601String(), + 'created_at' => self::formatTimestamp($model->created_at), ]; } } diff --git a/app/Transformers/Api/Client/WebauthnKeyTransformer.php b/app/Transformers/Api/Client/WebauthnKeyTransformer.php index 5a6056a83..062adb9fb 100644 --- a/app/Transformers/Api/Client/WebauthnKeyTransformer.php +++ b/app/Transformers/Api/Client/WebauthnKeyTransformer.php @@ -3,15 +3,16 @@ namespace Pterodactyl\Transformers\Api\Client; use Pterodactyl\Models\WebauthnKey; +use Pterodactyl\Transformers\Api\Transformer; -class WebauthnKeyTransformer extends BaseClientTransformer +class WebauthnKeyTransformer extends Transformer { /** * Return the resource name for the JSONAPI output. */ public function getResourceName(): string { - return 'webauthn_key'; + return WebauthnKey::RESOURCE_NAME; } /** @@ -22,8 +23,7 @@ class WebauthnKeyTransformer extends BaseClientTransformer return [ 'id' => $model->id, 'name' => $model->name, - 'created_at' => $model->created_at->toIso8601String(), - 'last_used_at' => now()->toIso8601String(), + 'created_at' => self::formatTimestamp($model->created_at), ]; } } diff --git a/app/Transformers/Api/Transformer.php b/app/Transformers/Api/Transformer.php new file mode 100644 index 000000000..91fdc0a3e --- /dev/null +++ b/app/Transformers/Api/Transformer.php @@ -0,0 +1,159 @@ +request = Container::getInstance()->make('request'); + + if (method_exists($this, 'handle')) { + Container::getInstance()->call([$this, 'handle']); + } + } + + /** + * Returns the resource name for the transformed item. + */ + abstract public function getResourceName(): string; + + /** + * Returns the authorized user for the request. + */ + protected function user(): User + { + return $this->request->user(); + } + + /** + * Determines if the user making this request is authorized to access the given + * resource on the API. This is used when requested included items to ensure that + * the user and key are authorized to see the result. + * + * TODO: implement this with the new API key formats. + */ + protected function authorize(string $resource): bool + { + return $this->request->user() instanceof User; + } + + /** + * {@inheritDoc} + * + * @param mixed $data + * @param callable|\League\Fractal\TransformerAbstract $transformer + * @param null $resourceKey + * + * @return \League\Fractal\Resource\Item + */ + protected function item($data, $transformer, $resourceKey = null) + { + if (!$transformer instanceof Closure) { + self::assertSameNamespace($transformer); + } + + $item = parent::item($data, $transformer, $resourceKey); + + if (!$item->getResourceKey()) { + $item->setResourceKey($transformer->getResourceName()); + } + + return $item; + } + + /** + * {@inheritDoc} + * + * @param mixed $data + * @param callable|\League\Fractal\TransformerAbstract $transformer + * @param null $resourceKey + * + * @return \League\Fractal\Resource\Collection + */ + protected function collection($data, $transformer, $resourceKey = null) + { + if (!$transformer instanceof Closure) { + self::assertSameNamespace($transformer); + } + + $collection = parent::collection($data, $transformer, $resourceKey); + + if (!$collection->getResourceKey()) { + $collection->setResourceKey($transformer->getResourceName()); + } + + return $collection; + } + + /** + * Sets the default timezone to use for transformed responses. Pass a null value + * to return back to the default timezone (UTC). + */ + public static function setTimezone(string $tz = null) + { + static::$timezone = $tz ?? 'UTC'; + } + + /** + * Asserts that the given transformer is the same base namespace as the class that + * implements this abstract transformer class. This prevents a client or application + * transformer from unintentionally transforming a resource using an unexpected type. + * + * @param callable|\League\Fractal\TransformerAbstract $transformer + */ + protected static function assertSameNamespace($transformer) + { + Assert::subclassOf($transformer, TransformerAbstract::class); + + $namespace = substr(get_class($transformer), 0, strlen(class_basename($transformer)) * -1); + $expected = substr(static::class, 0, strlen(class_basename(static::class)) * -1); + + Assert::same($namespace, $expected, 'Cannot invoke a new transformer (%s) that is not in the same namespace (%s).'); + } + + /** + * Returns an ISO-8601 formatted timestamp to use in API responses. This + * time is returned in the default transformer timezone if no timezone value + * is provided. + * + * If no time is provided a null value is returned. + * + * @param string|\DateTimeInterface|null $timestamp + */ + protected static function formatTimestamp($timestamp, string $tz = null): ?string + { + if (empty($timestamp)) { + return null; + } + + if ($timestamp instanceof DateTimeInterface) { + $value = CarbonImmutable::instance($timestamp); + } else { + $value = CarbonImmutable::createFromFormat(CarbonImmutable::DEFAULT_TO_STRING_FORMAT, $timestamp); + } + + return $value->setTimezone($tz ?? self::$timezone)->toIso8601String(); + } +} diff --git a/composer.json b/composer.json index 67cd53888..a7df91b3a 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "laracasts/utilities": "^3.2", "laravel/framework": "^8.52", "laravel/helpers": "^1.4", + "laravel/sanctum": "^2.11", "laravel/tinker": "^2.6", "laravel/ui": "^3.3", "lcobucci/jwt": "^4.1", diff --git a/composer.lock b/composer.lock index ab7275c0f..a5b07c281 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "15175f8e95c1d5f6e6d93fc832b01a02", + "content-hash": "1f3ffa50fe87faebdc49aa01fbbb364f", "packages": [ { "name": "asbiin/laravel-webauthn", @@ -1958,6 +1958,70 @@ }, "time": "2021-02-16T15:27:11+00:00" }, + { + "name": "laravel/sanctum", + "version": "v2.11.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "b21e65cbe13896946986cb0868180cd69e1bd5d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/b21e65cbe13896946986cb0868180cd69e1bd5d3", + "reference": "b21e65cbe13896946986cb0868180cd69e1bd5d3", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/contracts": "^6.9|^7.0|^8.0", + "illuminate/database": "^6.9|^7.0|^8.0", + "illuminate/support": "^6.9|^7.0|^8.0", + "php": "^7.2|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0", + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2021-06-15T15:56:21+00:00" + }, { "name": "laravel/tinker", "version": "v2.6.1", @@ -12601,5 +12665,5 @@ "ext-zip": "*" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.0.0" } diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 000000000..abf1a9322 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,32 @@ + ['api/*'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 7200, + + 'supports_credentials' => true, +]; diff --git a/config/database.php b/config/database.php index 681bd3e1a..ca82b79ec 100644 --- a/config/database.php +++ b/config/database.php @@ -118,7 +118,7 @@ return [ 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'), ], 'default' => [ diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 000000000..a2febb243 --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,49 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1,pterodactyl.test', + env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : '' + ))), + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. If this value is null, personal access tokens do + | not expire. This won't tweak the lifetime of first-party sessions. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'verify_csrf_token' => Pterodactyl\Http\Middleware\VerifyCsrfToken::class, + 'encrypt_cookies' => Pterodactyl\Http\Middleware\EncryptCookies::class, + ], +]; diff --git a/config/services.php b/config/services.php index b15f7bf02..39b8116c7 100644 --- a/config/services.php +++ b/config/services.php @@ -16,7 +16,7 @@ return [ 'mailgun' => [ 'domain' => env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET'), - 'endpoint' => env('MAILGUN_ENDPOINT') + 'endpoint' => env('MAILGUN_ENDPOINT'), ], 'mandrill' => [ diff --git a/config/webauthn.php b/config/webauthn.php index 623eb4b14..4d94f7f07 100644 --- a/config/webauthn.php +++ b/config/webauthn.php @@ -1,7 +1,6 @@ [ - /* | See https://www.w3.org/TR/webauthn/#attachment */ @@ -221,5 +219,4 @@ return [ */ 'user_verification' => \Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED, ], - ]; diff --git a/database/Factories/ApiKeyFactory.php b/database/Factories/ApiKeyFactory.php deleted file mode 100644 index 1faa4be55..000000000 --- a/database/Factories/ApiKeyFactory.php +++ /dev/null @@ -1,36 +0,0 @@ - ApiKey::TYPE_APPLICATION, - 'identifier' => Str::random(ApiKey::IDENTIFIER_LENGTH), - 'token' => $token ?: $token = encrypt(Str::random(ApiKey::KEY_LENGTH)), - 'allowed_ips' => null, - 'memo' => 'Test Function Key', - 'created_at' => Carbon::now(), - 'updated_at' => Carbon::now(), - ]; - } -} diff --git a/database/Factories/PersonalAccessTokenFactory.php b/database/Factories/PersonalAccessTokenFactory.php new file mode 100644 index 000000000..0d64d4cc8 --- /dev/null +++ b/database/Factories/PersonalAccessTokenFactory.php @@ -0,0 +1,28 @@ + PersonalAccessToken::generateTokenIdentifier(), + 'token' => hash('sha256', Str::random(PersonalAccessToken::TOKEN_LENGTH)), + 'description' => 'Generated test token', + 'abilities' => ['*'], + ]; + } +} diff --git a/database/migrations/2019_03_29_163611_add_webauthn.php b/database/migrations/2019_03_29_163611_add_webauthn.php index 5e9718a4d..80406d4c5 100644 --- a/database/migrations/2019_03_29_163611_add_webauthn.php +++ b/database/migrations/2019_03_29_163611_add_webauthn.php @@ -1,8 +1,8 @@ bigIncrements('id'); + $table->unsignedInteger('user_id'); + $table->char('token_id', 16)->unique(); + $table->string('token', 64)->unique(); + $table->json('abilities')->nullable(); + $table->string('description'); + $table->timestamp('last_used_at')->nullable(); + $table->timestamps(); + + $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('personal_access_tokens'); + } +} diff --git a/database/migrations/2020_09_25_021109_create_admin_roles_table.php b/database/migrations/2020_09_25_021109_create_admin_roles_table.php index e65a457b5..40a141f94 100644 --- a/database/migrations/2020_09_25_021109_create_admin_roles_table.php +++ b/database/migrations/2020_09_25_021109_create_admin_roles_table.php @@ -1,8 +1,8 @@ ({ - identifier: data.identifier, + identifier: data.token_id, description: data.description, - allowedIps: data.allowed_ips, createdAt: data.created_at ? new Date(data.created_at) : null, lastUsedAt: data.last_used_at ? new Date(data.last_used_at) : null, }); diff --git a/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx index 28490ddd2..3c3146a79 100644 --- a/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx +++ b/resources/scripts/components/dashboard/forms/CreateApiKeyForm.tsx @@ -8,9 +8,9 @@ import { ApplicationStore } from '@/state'; import { httpErrorToHuman } from '@/api/http'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import { ApiKey } from '@/api/account/getApiKeys'; -import tw, { styled } from 'twin.macro'; +import tw from 'twin.macro'; import Button from '@/components/elements/Button'; -import Input, { Textarea } from '@/components/elements/Input'; +import Input from '@/components/elements/Input'; import ApiKeyModal from '@/components/dashboard/ApiKeyModal'; interface Values { @@ -18,8 +18,6 @@ interface Values { allowedIps: string; } -const CustomTextarea = styled(Textarea)`${tw`h-32`}`; - export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { const [ apiKey, setApiKey ] = useState(''); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); @@ -30,7 +28,7 @@ export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { .then(({ secretToken, ...key }) => { resetForm(); setSubmitting(false); - setApiKey(`${key.identifier}${secretToken}`); + setApiKey(secretToken); onKeyCreated(key); }) .catch(error => { @@ -52,7 +50,6 @@ export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { onSubmit={submit} initialValues={{ description: '', allowedIps: '' }} validationSchema={object().shape({ - allowedIps: string(), description: string().required().min(4), })} > @@ -62,18 +59,11 @@ export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => { - - -
diff --git a/routes/api-application.php b/routes/api-application.php index 98f0f0a20..ef687a21b 100644 --- a/routes/api-application.php +++ b/routes/api-application.php @@ -132,7 +132,6 @@ Route::group(['prefix' => '/nodes'], function () { }); }); - /* |-------------------------------------------------------------------------- | Role Controller Routes @@ -152,7 +151,6 @@ Route::group(['prefix' => '/roles'], function () { Route::delete('/{role}', 'Roles\RoleController@delete'); }); - /* |-------------------------------------------------------------------------- | Server Controller Routes diff --git a/routes/api-client.php b/routes/api-client.php index 72b6729fe..44cef3162 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -1,12 +1,11 @@ '/account'], function () { Route::put('/email', 'AccountController@updateEmail')->name('api:client.account.update-email'); Route::put('/password', 'AccountController@updatePassword')->name('api:client.account.update-password'); - Route::get('/api-keys', 'ApiKeyController@index'); - Route::post('/api-keys', 'ApiKeyController@store'); - Route::delete('/api-keys/{identifier}', 'ApiKeyController@delete'); + Route::get('/api-keys', [Client\ApiKeyController::class, 'index']); + Route::post('/api-keys', [Client\ApiKeyController::class, 'store']); + Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']); Route::get('/webauthn', 'WebauthnController@index')->withoutMiddleware(RequireTwoFactorAuthentication::class); Route::get('/webauthn/register', 'WebauthnController@register')->withoutMiddleware(RequireTwoFactorAuthentication::class); @@ -49,7 +48,10 @@ Route::group(['prefix' => '/account'], function () { | Endpoint: /api/client/servers/{server} | */ -Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class, ResourceBelongsToServer::class]], function () { +Route::group([ + 'prefix' => '/servers/{server}', + 'middleware' => [AuthenticateServerAccess::class, ResourceBelongsToServer::class], +], function () { Route::get('/', 'Servers\ServerController@index')->name('api:client:server.view'); Route::get('/websocket', 'Servers\WebsocketController')->name('api:client:server.ws'); Route::get('/resources', 'Servers\ResourceUtilizationController')->name('api:client:server.resources'); @@ -104,9 +106,9 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ Route::group(['prefix' => '/users'], function () { Route::get('/', 'Servers\SubuserController@index'); Route::post('/', 'Servers\SubuserController@store'); - Route::get('/{user}', 'Servers\SubuserController@view'); - Route::post('/{user}', 'Servers\SubuserController@update'); - Route::delete('/{user}', 'Servers\SubuserController@delete'); + Route::get('/{subuser}', [Client\Servers\SubuserController::class, 'view']); + Route::post('/{subuser}', [Client\Servers\SubuserController::class, 'update']); + Route::delete('/{subuser}', [Client\Servers\SubuserController::class, 'delete']); }); Route::group(['prefix' => '/backups'], function () { diff --git a/tests/Browser/BrowserTestCase.php b/tests/Browser/BrowserTestCase.php index 68a6ddc1b..2c8e94864 100644 --- a/tests/Browser/BrowserTestCase.php +++ b/tests/Browser/BrowserTestCase.php @@ -33,7 +33,7 @@ abstract class BrowserTestCase extends TestCase * test. In most cases you probably wont need to do this, or can modify the test slightly to * avoid the need to do so. */ - public static function setUpBeforeClass() + public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); diff --git a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php index 53585e2e6..ab977dba4 100644 --- a/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php +++ b/tests/Integration/Api/Application/ApplicationApiIntegrationTestCase.php @@ -3,14 +3,10 @@ namespace Pterodactyl\Tests\Integration\Api\Application; use Pterodactyl\Models\User; -use PHPUnit\Framework\Assert; -use Pterodactyl\Models\ApiKey; -use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Models\PersonalAccessToken; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Illuminate\Foundation\Testing\DatabaseTransactions; use Pterodactyl\Tests\Traits\Integration\CreatesTestModels; -use Pterodactyl\Transformers\Api\Application\BaseTransformer; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; use Pterodactyl\Tests\Traits\Http\IntegrationJsonRequestAssertions; abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase @@ -19,16 +15,19 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase use DatabaseTransactions; use IntegrationJsonRequestAssertions; - /** - * @var \Pterodactyl\Models\ApiKey - */ - private $key; - /** * @var \Pterodactyl\Models\User */ private $user; + /** + * @var string[] + */ + protected $defaultHeaders = [ + 'Accept' => 'application/vnd.pterodactyl.v1+json', + 'Content-Type' => 'application/json', + ]; + /** * Bootstrap application API tests. Creates a default admin user and associated API key * and also sets some default headers required for accessing the API. @@ -37,111 +36,25 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase { parent::setUp(); - $this->user = $this->createApiUser(); - $this->key = $this->createApiKey($this->user); + $this->user = User::factory()->create(['root_admin' => true]); - $this->withHeader('Accept', 'application/vnd.pterodactyl.v1+json'); - $this->withHeader('Authorization', 'Bearer ' . $this->getApiKey()->identifier . decrypt($this->getApiKey()->token)); - - $this->withMiddleware('api..key:' . ApiKey::TYPE_APPLICATION); + $this->createNewAccessToken(); } - /** - * @return \Pterodactyl\Models\User - */ public function getApiUser(): User { return $this->user; } - /** - * @return \Pterodactyl\Models\ApiKey - */ - public function getApiKey(): ApiKey - { - return $this->key; - } - /** * Creates a new default API key and refreshes the headers using it. - * - * @param \Pterodactyl\Models\User $user - * @param array $permissions - * - * @return \Pterodactyl\Models\ApiKey */ - protected function createNewDefaultApiKey(User $user, array $permissions = []): ApiKey + protected function createNewAccessToken(array $abilities = ['*']): PersonalAccessToken { - $this->key = $this->createApiKey($user, $permissions); - $this->refreshHeaders($this->key); + $token = $this->user->createToken('test', $abilities); - return $this->key; - } + $this->withHeader('Authorization', 'Bearer ' . $token->plainTextToken); - /** - * Refresh the authorization header for a request to use a different API key. - * - * @param \Pterodactyl\Models\ApiKey $key - */ - protected function refreshHeaders(ApiKey $key) - { - $this->withHeader('Authorization', 'Bearer ' . $key->identifier . decrypt($key->token)); - } - - /** - * Create an administrative user. - * - * @return \Pterodactyl\Models\User - */ - protected function createApiUser(): User - { - return User::factory()->create([ - 'root_admin' => true, - ]); - } - - /** - * Create a new application API key for a given user model. - * - * @param \Pterodactyl\Models\User $user - * @param array $permissions - * - * @return \Pterodactyl\Models\ApiKey - */ - protected function createApiKey(User $user, array $permissions = []): ApiKey - { - return ApiKey::factory()->create(array_merge([ - 'user_id' => $user->id, - 'key_type' => ApiKey::TYPE_APPLICATION, - 'r_servers' => AdminAcl::READ | AdminAcl::WRITE, - 'r_nodes' => AdminAcl::READ | AdminAcl::WRITE, - 'r_allocations' => AdminAcl::READ | AdminAcl::WRITE, - 'r_users' => AdminAcl::READ | AdminAcl::WRITE, - 'r_locations' => AdminAcl::READ | AdminAcl::WRITE, - 'r_nests' => AdminAcl::READ | AdminAcl::WRITE, - 'r_eggs' => AdminAcl::READ | AdminAcl::WRITE, - 'r_database_hosts' => AdminAcl::READ | AdminAcl::WRITE, - 'r_server_databases' => AdminAcl::READ | AdminAcl::WRITE, - ], $permissions)); - } - - /** - * Return a transformer that can be used for testing purposes. - * - * @param string $abstract - * - * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer - * @throws \Illuminate\Contracts\Container\BindingResolutionException - */ - protected function getTransformer(string $abstract): BaseTransformer - { - /** @var \Pterodactyl\Transformers\Api\Application\BaseTransformer $transformer */ - $transformer = $this->app->make($abstract); - $transformer->setKey($this->getApiKey()); - - Assert::assertInstanceOf(BaseTransformer::class, $transformer); - Assert::assertNotInstanceOf(BaseClientTransformer::class, $transformer); - - return $transformer; + return $token->accessToken; } } diff --git a/tests/Integration/Api/Application/Eggs/EggControllerTest.php b/tests/Integration/Api/Application/Eggs/EggControllerTest.php index 3beb536fe..05608a831 100644 --- a/tests/Integration/Api/Application/Eggs/EggControllerTest.php +++ b/tests/Integration/Api/Application/Eggs/EggControllerTest.php @@ -56,7 +56,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase $egg = $eggs->where('id', '=', $datum['attributes']['id'])->first(); $expected = json_encode(Arr::sortRecursive($datum['attributes'])); - $actual = json_encode(Arr::sortRecursive($this->getTransformer(EggTransformer::class)->transform($egg))); + $actual = json_encode(Arr::sortRecursive((new EggTransformer())->transform($egg))); $this->assertSame( $expected, @@ -84,7 +84,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase $response->assertJson([ 'object' => 'egg', - 'attributes' => $this->getTransformer(EggTransformer::class)->transform($egg), + 'attributes' => (new EggTransformer())->transform($egg), ], true); } @@ -124,11 +124,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $egg = $this->repository->find(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); - - $response = $this->getJson('/api/application/nests/' . $egg->nest_id . '/eggs'); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -137,9 +133,6 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase */ public function testResourceIsNotExposedWithoutPermissions() { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_eggs' => 0]); - - $response = $this->getJson('/api/application/eggs/nil'); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index 44aee54a9..9dd7e0d88 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -118,7 +118,7 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase 'data' => [ [ 'object' => 'node', - 'attributes' => $this->getTransformer(NodeTransformer::class)->transform($server->getRelation('node')), + 'attributes' => (new NodeTransformer())->transform($server->getRelation('node')), ], ], ], @@ -127,7 +127,7 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase 'data' => [ [ 'object' => 'server', - 'attributes' => $this->getTransformer(ServerTransformer::class)->transform($server), + 'attributes' => (new ServerTransformer())->transform($server), ], ], ], @@ -142,33 +142,7 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase */ public function testKeyWithoutPermissionCannotLoadRelationship() { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_nodes' => 0]); - - $location = Location::factory()->create(); - Node::factory()->create(['location_id' => $location->id]); - - $response = $this->getJson('/api/application/locations/' . $location->id . '?include=nodes'); - $response->assertStatus(Response::HTTP_OK); - $response->assertJsonCount(2)->assertJsonCount(1, 'attributes.relationships'); - $response->assertJsonStructure([ - 'attributes' => [ - 'relationships' => [ - 'nodes' => ['object', 'attributes'], - ], - ], - ]); - - // Just assert that we see the expected relationship IDs in the response. - $response->assertJson([ - 'attributes' => [ - 'relationships' => [ - 'nodes' => [ - 'object' => 'null_resource', - 'attributes' => null, - ], - ], - ], - ]); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -188,11 +162,7 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $location = Location::factory()->create(); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_locations' => 0]); - - $response = $this->getJson('/api/application/locations/' . $location->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -201,9 +171,6 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase */ public function testResourceIsNotExposedWithoutPermissions() { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_locations' => 0]); - - $response = $this->getJson('/api/application/locations/nil'); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Nests/NestControllerTest.php b/tests/Integration/Api/Application/Nests/NestControllerTest.php index 7aba83c2f..1901d96a3 100644 --- a/tests/Integration/Api/Application/Nests/NestControllerTest.php +++ b/tests/Integration/Api/Application/Nests/NestControllerTest.php @@ -59,7 +59,7 @@ class NestControllerTest extends ApplicationApiIntegrationTestCase foreach ($nests as $nest) { $response->assertJsonFragment([ 'object' => 'nest', - 'attributes' => $this->getTransformer(NestTransformer::class)->transform($nest), + 'attributes' => (new NestTransformer())->transform($nest), ]); } } @@ -80,7 +80,7 @@ class NestControllerTest extends ApplicationApiIntegrationTestCase $response->assertJson([ 'object' => 'nest', - 'attributes' => $this->getTransformer(NestTransformer::class)->transform($nest), + 'attributes' => (new NestTransformer())->transform($nest), ]); } @@ -122,11 +122,7 @@ class NestControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $nest = $this->repository->find(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_nests' => 0]); - - $response = $this->getJson('/api/application/nests/' . $nest->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -135,10 +131,6 @@ class NestControllerTest extends ApplicationApiIntegrationTestCase */ public function testResourceIsNotExposedWithoutPermissions() { - $nest = $this->repository->find(1); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_nests' => 0]); - - $response = $this->getJson('/api/application/nests/' . $nest->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php index f634931c1..d5b59de1f 100644 --- a/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php +++ b/tests/Integration/Api/Application/Users/ExternalUserControllerTest.php @@ -58,11 +58,7 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $user = User::factory()->create(); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/external/' . $user->external_id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -71,9 +67,6 @@ class ExternalUserControllerTest extends ApplicationApiIntegrationTestCase */ public function testResourceIsNotExposedWithoutPermissions() { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/external/nil'); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } } diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index cf88deeda..e2dbe97d4 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -16,7 +16,8 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase */ public function testGetUsers() { - $user = User::factory()->create(); + $user = $this->getApiUser(); + $created = User::factory()->create(); $response = $this->getJson('/api/application/users'); $response->assertStatus(Response::HTTP_OK); @@ -45,24 +46,6 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase ], ], ]) - ->assertJsonFragment([ - 'object' => 'user', - 'attributes' => [ - 'id' => $this->getApiUser()->id, - 'external_id' => $this->getApiUser()->external_id, - 'uuid' => $this->getApiUser()->uuid, - 'username' => $this->getApiUser()->username, - 'email' => $this->getApiUser()->email, - 'language' => $this->getApiUser()->language, - 'admin_role_id' => $this->getApiUser()->admin_role_id, - 'root_admin' => (bool) $this->getApiUser()->root_admin, - '2fa' => (bool) $this->getApiUser()->totp_enabled, - 'avatar_url' => $this->getApiUser()->avatarURL(), - 'role_name' => $this->getApiUser()->adminRoleName(), - 'created_at' => $this->formatTimestamp($this->getApiUser()->created_at), - 'updated_at' => $this->formatTimestamp($this->getApiUser()->updated_at), - ], - ]) ->assertJsonFragment([ 'object' => 'user', 'attributes' => [ @@ -80,6 +63,24 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'created_at' => $this->formatTimestamp($user->created_at), 'updated_at' => $this->formatTimestamp($user->updated_at), ], + ]) + ->assertJsonFragment([ + 'object' => 'user', + 'attributes' => [ + 'id' => $created->id, + 'external_id' => $created->external_id, + 'uuid' => $created->uuid, + 'username' => $created->username, + 'email' => $created->email, + 'language' => $created->language, + 'admin_role_id' => $created->admin_role_id, + 'root_admin' => (bool) $created->root_admin, + '2fa' => (bool) $created->totp_enabled, + 'avatar_url' => $created->avatarURL(), + 'role_name' => $created->adminRoleName(), + 'created_at' => $this->formatTimestamp($created->created_at), + 'updated_at' => $this->formatTimestamp($created->updated_at), + ], ]); } @@ -140,7 +141,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'data' => [ [ 'object' => 'server', - 'attributes' => $this->getTransformer(ServerTransformer::class)->transform($server), + 'attributes' => (new ServerTransformer())->transform($server), ], ], ]); @@ -152,33 +153,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase */ public function testKeyWithoutPermissionCannotLoadRelationship() { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_servers' => 0]); - - $user = User::factory()->create(); - $this->createServerModel(['user_id' => $user->id]); - - $response = $this->getJson('/api/application/users/' . $user->id . '?include=servers'); - $response->assertStatus(Response::HTTP_OK); - $response->assertJsonCount(2)->assertJsonCount(1, 'attributes.relationships'); - $response->assertJsonStructure([ - 'attributes' => [ - 'relationships' => [ - 'servers' => ['object', 'attributes'], - ], - ], - ]); - - // Just assert that we see the expected relationship IDs in the response. - $response->assertJson([ - 'attributes' => [ - 'relationships' => [ - 'servers' => [ - 'object' => 'null_resource', - 'attributes' => null, - ], - ], - ], - ]); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -196,11 +171,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase */ public function testErrorReturnedIfNoPermission() { - $user = User::factory()->create(); - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/' . $user->id); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -209,10 +180,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase */ public function testResourceIsNotExposedWithoutPermissions() { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => 0]); - - $response = $this->getJson('/api/application/users/nil'); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** @@ -238,7 +206,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $user = User::where('username', 'testuser')->first(); $response->assertJson([ 'object' => 'user', - 'attributes' => $this->getTransformer(UserTransformer::class)->transform($user), + 'attributes' => (new UserTransformer())->transform($user), 'meta' => [ 'resource' => route('api.application.users.view', $user->id), ], @@ -268,7 +236,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase $response->assertJson([ 'object' => 'user', - 'attributes' => $this->getTransformer(UserTransformer::class)->transform($user), + 'attributes' => (new UserTransformer())->transform($user), ]); } @@ -294,15 +262,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase */ public function testApiKeyWithoutWritePermissions(string $method, string $url) { - $this->createNewDefaultApiKey($this->getApiUser(), ['r_users' => AdminAcl::READ]); - - if (str_contains($url, '{id}')) { - $user = User::factory()->create(); - $url = str_replace('{id}', $user->id, $url); - } - - $response = $this->$method($url); - $this->assertAccessDeniedJson($response); + $this->markTestSkipped('todo: implement proper admin api key permissions system'); } /** diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php index 77bb8e1a6..5d736ecaf 100644 --- a/tests/Integration/Api/Client/ApiKeyControllerTest.php +++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php @@ -4,7 +4,8 @@ namespace Pterodactyl\Tests\Integration\Api\Client; use Pterodactyl\Models\User; use Illuminate\Http\Response; -use Pterodactyl\Models\ApiKey; +use Pterodactyl\Models\PersonalAccessToken; +use Pterodactyl\Transformers\Api\Client\PersonalAccessTokenTransformer; class ApiKeyControllerTest extends ClientApiIntegrationTestCase { @@ -13,7 +14,7 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase */ protected function tearDown(): void { - ApiKey::query()->forceDelete(); + PersonalAccessToken::query()->forceDelete(); parent::tearDown(); } @@ -25,11 +26,8 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase { /** @var \Pterodactyl\Models\User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::factory()->create([ - 'user_id' => $user->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); + $token = $user->createToken('test'); + $token = $token->accessToken; $response = $this->actingAs($user)->get('/api/client/account/api-keys'); @@ -38,13 +36,14 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase 'object' => 'list', 'data' => [ [ - 'object' => 'api_key', + 'object' => 'personal_access_token', 'attributes' => [ - 'identifier' => $key->identifier, - 'description' => $key->memo, - 'allowed_ips' => $key->allowed_ips, + 'token_id' => $token->token_id, + 'description' => $token->description, + 'abilities' => ['*'], 'last_used_at' => null, - 'created_at' => $key->created_at->toIso8601String(), + 'updated_at' => $this->formatTimestamp($token->updated_at), + 'created_at' => $this->formatTimestamp($token->created_at), ], ], ], @@ -64,34 +63,24 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase // Small sub-test to ensure we're always comparing the number of keys to the // specific logged in account, and not just the total number of keys stored in // the database. - ApiKey::factory()->times(10)->create([ + PersonalAccessToken::factory()->times(10)->create([ 'user_id' => User::factory()->create()->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, ]); $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [ 'description' => 'Test Description', - 'allowed_ips' => ['127.0.0.1'], ]); $response->assertOk(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::query()->where('identifier', $response->json('attributes.identifier'))->firstOrFail(); + $key = PersonalAccessToken::query()->where('token_id', $response->json('attributes.token_id'))->firstOrFail(); $response->assertJson([ - 'object' => 'api_key', - 'attributes' => [ - 'identifier' => $key->identifier, - 'description' => 'Test Description', - 'allowed_ips' => ['127.0.0.1'], - 'last_used_at' => null, - 'created_at' => $key->created_at->toIso8601String(), - ], - 'meta' => [ - 'secret_token' => decrypt($key->token), - ], + 'object' => 'personal_access_token', + 'attributes' => (new PersonalAccessTokenTransformer())->transform($key), ]); + + $this->assertEquals($key->token, hash('sha256', substr($response->json('meta.secret_token'), 16))); } /** @@ -104,14 +93,10 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase { /** @var \Pterodactyl\Models\User $user */ $user = User::factory()->create(); - ApiKey::factory()->times(5)->create([ - 'user_id' => $user->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); + PersonalAccessToken::factory()->times(10)->create(['user_id' => $user->id]); $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [ 'description' => 'Test Description', - 'allowed_ips' => ['127.0.0.1'], ]); $response->assertStatus(Response::HTTP_BAD_REQUEST); @@ -131,7 +116,6 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [ 'description' => '', - 'allowed_ips' => ['127.0.0.1'], ]); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -140,7 +124,6 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [ 'description' => str_repeat('a', 501), - 'allowed_ips' => ['127.0.0.1'], ]); $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -155,16 +138,12 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase { /** @var \Pterodactyl\Models\User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::factory()->create([ - 'user_id' => $user->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); + $token = $user->createToken('test'); - $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $key->identifier); - $response->assertStatus(Response::HTTP_NO_CONTENT); + $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $token->accessToken->token_id); + $response->assertNoContent(); - $this->assertDatabaseMissing('api_keys', ['id' => $key->id]); + $this->assertDatabaseMissing('personal_access_tokens', ['id' => $token->accessToken->id]); } /** @@ -174,16 +153,12 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase { /** @var \Pterodactyl\Models\User $user */ $user = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::factory()->create([ - 'user_id' => $user->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); + $token = $user->createToken('test'); - $response = $this->actingAs($user)->delete('/api/client/account/api-keys/1234'); - $response->assertNotFound(); + $response = $this->actingAs($user)->delete('/api/client/account/api-keys/ptdl_1234'); + $response->assertNoContent(); - $this->assertDatabaseHas('api_keys', ['id' => $key->id]); + $this->assertDatabaseHas('personal_access_tokens', ['id' => $token->accessToken->id]); } /** @@ -196,35 +171,11 @@ class ApiKeyControllerTest extends ClientApiIntegrationTestCase $user = User::factory()->create(); /** @var \Pterodactyl\Models\User $user2 */ $user2 = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::factory()->create([ - 'user_id' => $user2->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); + $token = $user2->createToken('test'); - $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $key->identifier); - $response->assertNotFound(); + $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $token->accessToken->token_id); + $response->assertNoContent(); - $this->assertDatabaseHas('api_keys', ['id' => $key->id]); - } - - /** - * Tests that an application API key also belonging to the logged in user cannot be - * deleted through this endpoint if it exists. - */ - public function testApplicationApiKeyCannotBeDeleted() - { - /** @var \Pterodactyl\Models\User $user */ - $user = User::factory()->create(); - /** @var \Pterodactyl\Models\ApiKey $key */ - $key = ApiKey::factory()->create([ - 'user_id' => $user->id, - 'key_type' => ApiKey::TYPE_APPLICATION, - ]); - - $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $key->identifier); - $response->assertNotFound(); - - $this->assertDatabaseHas('api_keys', ['id' => $key->id]); + $this->assertDatabaseHas('personal_access_tokens', ['id' => $token->accessToken->id]); } } diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index 8075c9fc2..0ec5fa48f 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -6,7 +6,6 @@ use ReflectionClass; use Pterodactyl\Models\Node; use Pterodactyl\Models\Task; use Pterodactyl\Models\User; -use Webmozart\Assert\Assert; use InvalidArgumentException; use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; @@ -19,7 +18,6 @@ use Pterodactyl\Models\Allocation; use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Tests\Integration\TestResponse; use Pterodactyl\Tests\Integration\IntegrationTestCase; -use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; abstract class ClientApiIntegrationTestCase extends IntegrationTestCase { @@ -61,7 +59,6 @@ abstract class ClientApiIntegrationTestCase extends IntegrationTestCase */ protected function link($model, $append = null): string { - $link = ''; switch (get_class($model)) { case Server::class: $link = "/api/client/servers/{$model->uuid}"; @@ -100,7 +97,6 @@ abstract class ClientApiIntegrationTestCase extends IntegrationTestCase return [$user, $this->createServerModel(['user_id' => $user->id])]; } - /** @var \Pterodactyl\Models\Server $server */ $server = $this->createServerModel(); Subuser::query()->create([ @@ -124,7 +120,6 @@ abstract class ClientApiIntegrationTestCase extends IntegrationTestCase $transformer = sprintf('\\Pterodactyl\\Transformers\\Api\\Client\\%sTransformer', $reflect->getShortName()); $transformer = new $transformer(); - $this->assertInstanceOf(BaseClientTransformer::class, $transformer); $this->assertSame( $transformer->transform($model), diff --git a/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php index d79173fb8..b031b4fb2 100644 --- a/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php +++ b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php @@ -89,7 +89,7 @@ class CreateServerScheduleTaskTest extends ClientApiIntegrationTestCase } /** - * Test that backups can not be tasked when the backup limit is 0 + * Test that backups can not be tasked when the backup limit is 0. */ public function testBackupsCanNotBeTaskedIfLimit0() { diff --git a/tests/Integration/Api/Client/Server/WebsocketControllerTest.php b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php index d2656015e..6bd184cd6 100644 --- a/tests/Integration/Api/Client/Server/WebsocketControllerTest.php +++ b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php @@ -23,7 +23,7 @@ class WebsocketControllerTest extends ClientApiIntegrationTestCase $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket") ->assertStatus(Response::HTTP_FORBIDDEN) - ->assertJsonPath('errors.0.code', 'HttpForbiddenException') + ->assertJsonPath('errors.0.code', 'AccessDeniedHttpException') ->assertJsonPath('errors.0.detail', 'You do not have permission to connect to this server\'s websocket.'); } diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php index f12eaaef1..d8cea4b54 100644 --- a/tests/Integration/IntegrationTestCase.php +++ b/tests/Integration/IntegrationTestCase.php @@ -6,7 +6,6 @@ use Carbon\CarbonImmutable; use Pterodactyl\Tests\TestCase; use Illuminate\Database\Eloquent\Model; use Pterodactyl\Tests\Traits\Integration\CreatesTestModels; -use Pterodactyl\Transformers\Api\Application\BaseTransformer; abstract class IntegrationTestCase extends TestCase { @@ -40,7 +39,7 @@ abstract class IntegrationTestCase extends TestCase protected function formatTimestamp(string $timestamp): string { return CarbonImmutable::createFromFormat(CarbonImmutable::DEFAULT_TO_STRING_FORMAT, $timestamp) - ->setTimezone(BaseTransformer::RESPONSE_TIMEZONE) + ->setTimezone('UTC') ->toIso8601String(); } } diff --git a/tests/Integration/Jobs/Schedule/RunTaskJobTest.php b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php index bd943cbc9..68249dc43 100644 --- a/tests/Integration/Jobs/Schedule/RunTaskJobTest.php +++ b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php @@ -3,12 +3,11 @@ namespace Pterodactyl\Tests\Integration\Jobs\Schedule; use Mockery; -use Exception; use Carbon\CarbonImmutable; -use Pterodactyl\Models\Task; use GuzzleHttp\Psr7\Request; -use InvalidArgumentException; +use Pterodactyl\Models\Task; use GuzzleHttp\Psr7\Response; +use InvalidArgumentException; use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; use Illuminate\Support\Facades\Bus; diff --git a/tests/Integration/Services/Servers/BuildModificationServiceTest.php b/tests/Integration/Services/Servers/BuildModificationServiceTest.php index 9a61caa34..88be5d8fc 100644 --- a/tests/Integration/Services/Servers/BuildModificationServiceTest.php +++ b/tests/Integration/Services/Servers/BuildModificationServiceTest.php @@ -8,7 +8,6 @@ use GuzzleHttp\Psr7\Response; use Pterodactyl\Models\Server; use Pterodactyl\Models\Allocation; use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Pterodactyl\Repositories\Wings\DaemonServerRepository; diff --git a/tests/Traits/Http/IntegrationJsonRequestAssertions.php b/tests/Traits/Http/IntegrationJsonRequestAssertions.php index e26235a92..2658520ee 100644 --- a/tests/Traits/Http/IntegrationJsonRequestAssertions.php +++ b/tests/Traits/Http/IntegrationJsonRequestAssertions.php @@ -20,7 +20,7 @@ trait IntegrationJsonRequestAssertions [ 'code' => 'NotFoundHttpException', 'status' => '404', - 'detail' => 'The requested resource does not exist on this server.', + 'detail' => 'The requested resource could not be found on the server.', ], ], ], true); diff --git a/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php b/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php deleted file mode 100644 index c362e9cca..000000000 --- a/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php +++ /dev/null @@ -1,73 +0,0 @@ -make(['allowed_ips' => []]); - $this->setRequestAttribute('api_key', $model); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test middleware works correctly when a valid IP accesses - * and there is an IP restriction. - */ - public function testWithValidIP() - { - $model = ApiKey::factory()->make(['allowed_ips' => ['127.0.0.1']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->once()->andReturn('127.0.0.1'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that a CIDR range can be used. - */ - public function testValidIPAgainstCIDRRange() - { - $model = ApiKey::factory()->make(['allowed_ips' => ['192.168.1.1/28']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->once()->andReturn('192.168.1.15'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Test that an exception is thrown when an invalid IP address - * tries to connect and there is an IP restriction. - */ - public function testWithInvalidIP() - { - $this->expectException(AccessDeniedHttpException::class); - - $model = ApiKey::factory()->make(['allowed_ips' => ['127.0.0.1']]); - $this->setRequestAttribute('api_key', $model); - - $this->request->shouldReceive('ip')->withNoArgs()->twice()->andReturn('127.0.0.2'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Return an instance of the middleware to be used when testing. - */ - private function getMiddleware(): AuthenticateIPAccess - { - return new AuthenticateIPAccess(); - } -} diff --git a/tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php b/tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php deleted file mode 100644 index 211e810df..000000000 --- a/tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php +++ /dev/null @@ -1,168 +0,0 @@ -auth = m::mock(AuthManager::class); - $this->encrypter = m::mock(Encrypter::class); - $this->repository = m::mock(ApiKeyRepositoryInterface::class); - } - - /** - * Test that a missing bearer token will throw an exception. - */ - public function testMissingBearerTokenThrowsException() - { - $this->request->shouldReceive('user')->andReturnNull(); - $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturnNull(); - - try { - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION); - } catch (HttpException $exception) { - $this->assertEquals(401, $exception->getStatusCode()); - $this->assertEquals(['WWW-Authenticate' => 'Bearer'], $exception->getHeaders()); - } - } - - /** - * Test that an invalid API identifier throws an exception. - */ - public function testInvalidIdentifier() - { - $this->expectException(AccessDeniedHttpException::class); - - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn('abcd1234'); - $this->repository->shouldReceive('findFirstWhere')->andThrow(new RecordNotFoundException()); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION); - } - - /** - * Test that a valid token can continue past the middleware. - */ - public function testValidToken() - { - $model = ApiKey::factory()->make(); - - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted'); - $this->repository->shouldReceive('findFirstWhere')->with([ - ['identifier', '=', $model->identifier], - ['key_type', '=', ApiKey::TYPE_APPLICATION], - ])->once()->andReturn($model); - $this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted'); - $this->auth->shouldReceive('guard->loginUsingId')->with($model->user_id)->once()->andReturnNull(); - - $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ - 'last_used_at' => CarbonImmutable::now(), - ])->once()->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION); - $this->assertEquals($model, $this->request->attributes->get('api_key')); - } - - /** - * Test that a valid token can continue past the middleware when set as a user token. - */ - public function testValidTokenWithUserKey() - { - $model = ApiKey::factory()->make(); - - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted'); - $this->repository->shouldReceive('findFirstWhere')->with([ - ['identifier', '=', $model->identifier], - ['key_type', '=', ApiKey::TYPE_ACCOUNT], - ])->once()->andReturn($model); - $this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted'); - $this->auth->shouldReceive('guard->loginUsingId')->with($model->user_id)->once()->andReturnNull(); - - $this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [ - 'last_used_at' => CarbonImmutable::now(), - ])->once()->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_ACCOUNT); - $this->assertEquals($model, $this->request->attributes->get('api_key')); - } - - /** - * Test that we can still make it though this middleware if the user is logged in and passing - * through a cookie. - */ - public function testAccessWithoutToken() - { - $user = User::factory()->make(['id' => 123]); - - $this->request->shouldReceive('user')->andReturn($user); - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_ACCOUNT); - $model = $this->request->attributes->get('api_key'); - - $this->assertSame(ApiKey::TYPE_ACCOUNT, $model->key_type); - $this->assertSame(123, $model->user_id); - $this->assertNull($model->identifier); - } - - /** - * Test that a valid token identifier with an invalid token attached to it - * triggers an exception. - */ - public function testInvalidTokenForIdentifier() - { - $this->expectException(AccessDeniedHttpException::class); - - $model = ApiKey::factory()->make(); - - $this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'asdf'); - $this->repository->shouldReceive('findFirstWhere')->with([ - ['identifier', '=', $model->identifier], - ['key_type', '=', ApiKey::TYPE_APPLICATION], - ])->once()->andReturn($model); - $this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted'); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION); - } - - /** - * Return an instance of the middleware with mocked dependencies for testing. - */ - private function getMiddleware(): AuthenticateKey - { - return new AuthenticateKey($this->repository, $this->auth, $this->encrypter); - } -} diff --git a/tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php b/tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php deleted file mode 100644 index 83fc2b7bc..000000000 --- a/tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php +++ /dev/null @@ -1,44 +0,0 @@ -config = m::mock(Repository::class); - } - - /** - * Test that a production environment does not try to disable debug bar. - */ - public function testMiddleware() - { - $this->config->shouldReceive('set')->once()->with('session.driver', 'array')->andReturnNull(); - - $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); - } - - /** - * Return an instance of the middleware with mocked dependencies for testing. - */ - private function getMiddleware(): SetSessionDriver - { - return new SetSessionDriver($this->config); - } -} diff --git a/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php b/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php index 9b4633f2f..2f984e2b9 100644 --- a/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php +++ b/tests/Unit/Http/Middleware/RequireTwoFactorAuthenticationTest.php @@ -6,8 +6,8 @@ use Mockery as m; use Pterodactyl\Models\User; use Pterodactyl\Models\WebauthnKey; use Prologue\Alerts\AlertsMessageBag; -use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException; +use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase { @@ -23,7 +23,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->alerts = m::mock(AlertsMessageBag::class); } - public function testNoRequirement__userWithout_2fa() + public function testNoRequirementUserWithout2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_NONE); @@ -41,7 +41,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testNoRequirement__userWithTotp_2fa() + public function testNoRequirementUserWithTotp2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_NONE); @@ -59,7 +59,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testNoRequirement__userWithWebauthn_2fa() + public function testNoRequirementUserWithWebauthn2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_NONE); @@ -82,7 +82,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testNoRequirement__guestUser() + public function testNoRequirementGuestUser() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_NONE); @@ -96,7 +96,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAllRequirement__userWithout_2fa() + public function testAllRequirementUserWithout2fa() { $this->expectException(TwoFactorAuthRequiredException::class); @@ -116,7 +116,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAllRequirement__userWithTotp_2fa() + public function testAllRequirementUserWithTotp2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ALL); @@ -134,7 +134,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAllRequirement__ruserWithWebauthn_2fa() + public function testAllRequirementRuserWithWebauthn2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ALL); @@ -157,7 +157,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAllRequirement__guestUser() + public function testAllRequirementGuestUser() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ALL); @@ -171,7 +171,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAdminRequirement__userWithout_2fa() + public function testAdminRequirementUserWithout2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); @@ -190,7 +190,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAdminRequirement__adminUserWithout_2fa() + public function testAdminRequirementAdminUserWithout2fa() { $this->expectException(TwoFactorAuthRequiredException::class); @@ -211,7 +211,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAdminRequirement__userWithTotp_2fa() + public function testAdminRequirementUserWithTotp2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); @@ -230,7 +230,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAdminRequirement__adminUserWithTotp_2fa() + public function testAdminRequirementAdminUserWithTotp2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); @@ -249,7 +249,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAdminRequirement__userWithWebauthn_2fa() + public function testAdminRequirementUserWithWebauthn2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); @@ -271,7 +271,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAdminRequirement__adminUserWithWebauthn_2fa() + public function testAdminRequirementAdminUserWithWebauthn2fa() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); @@ -295,7 +295,7 @@ class RequireTwoFactorAuthenticationTest extends MiddlewareTestCase $this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); } - public function testAdminRequirement__guestUser() + public function testAdminRequirementGuestUser() { // Disable the 2FA requirement config()->set('pterodactyl.auth.2fa_required', RequireTwoFactorAuthentication::LEVEL_ADMIN); diff --git a/tests/Unit/Services/Acl/Api/AdminAclTest.php b/tests/Unit/Services/Acl/Api/AdminAclTest.php deleted file mode 100644 index daaea838f..000000000 --- a/tests/Unit/Services/Acl/Api/AdminAclTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertSame($outcome, AdminAcl::can($permission, $check)); - } - - /** - * Test that checking against a model works as expected. - */ - public function testCheck() - { - $model = ApiKey::factory()->make(['r_servers' => AdminAcl::READ | AdminAcl::WRITE]); - - $this->assertTrue(AdminAcl::check($model, AdminAcl::RESOURCE_SERVERS, AdminAcl::WRITE)); - } - - /** - * Provide valid and invalid permissions combos for testing. - */ - public function permissionsDataProvider(): array - { - return [ - [AdminAcl::READ, AdminAcl::READ, true], - [AdminAcl::READ | AdminAcl::WRITE, AdminAcl::READ, true], - [AdminAcl::READ | AdminAcl::WRITE, AdminAcl::WRITE, true], - [AdminAcl::WRITE, AdminAcl::WRITE, true], - [AdminAcl::READ, AdminAcl::WRITE, false], - [AdminAcl::NONE, AdminAcl::READ, false], - [AdminAcl::NONE, AdminAcl::WRITE, false], - ]; - } -} diff --git a/tests/Unit/Services/Api/KeyCreationServiceTest.php b/tests/Unit/Services/Api/KeyCreationServiceTest.php deleted file mode 100644 index 5c847f144..000000000 --- a/tests/Unit/Services/Api/KeyCreationServiceTest.php +++ /dev/null @@ -1,167 +0,0 @@ -encrypter = m::mock(Encrypter::class); - $this->repository = m::mock(ApiKeyRepositoryInterface::class); - } - - /** - * Test that the service is able to create a keypair and assign the correct permissions. - */ - public function testKeyIsCreated() - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'test-data' => 'test', - 'key_type' => ApiKey::TYPE_NONE, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->handle(['test-data' => 'test']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that an identifier is only set by the function. - */ - public function testIdentifierAndTokenAreOnlySetByFunction() - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'key_type' => ApiKey::TYPE_NONE, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->handle(['identifier' => 'customIdentifier', 'token' => 'customToken']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that permissions passed in are loaded onto the key data. - */ - public function testPermissionsAreRetrievedForApplicationKeys() - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'key_type' => ApiKey::TYPE_APPLICATION, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - 'permission-key' => 'exists', - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->setKeyType(ApiKey::TYPE_APPLICATION)->handle([], ['permission-key' => 'exists']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Test that permissions are not retrieved for any key that is not an application key. - * - * @dataProvider keyTypeDataProvider - */ - public function testPermissionsAreNotRetrievedForNonApplicationKeys($keyType) - { - $model = ApiKey::factory()->make(); - - $this->getFunctionMock('\\Pterodactyl\\Services\\Api', 'str_random') - ->expects($this->exactly(2))->willReturnCallback(function ($length) { - return 'str_' . $length; - }); - - $this->encrypter->shouldReceive('encrypt')->with('str_' . ApiKey::KEY_LENGTH)->once()->andReturn($model->token); - - $this->repository->shouldReceive('create')->with([ - 'key_type' => $keyType, - 'identifier' => 'str_' . ApiKey::IDENTIFIER_LENGTH, - 'token' => $model->token, - ], true, true)->once()->andReturn($model); - - $response = $this->getService()->setKeyType($keyType)->handle([], ['fake-permission' => 'should-not-exist']); - - $this->assertNotEmpty($response); - $this->assertInstanceOf(ApiKey::class, $response); - $this->assertSame($model, $response); - } - - /** - * Provide key types that are not an application specific key. - */ - public function keyTypeDataProvider(): array - { - return [ - [ApiKey::TYPE_NONE], [ApiKey::TYPE_ACCOUNT], [ApiKey::TYPE_DAEMON_USER], [ApiKey::TYPE_DAEMON_APPLICATION], - ]; - } - - /** - * Return an instance of the service with mocked dependencies for testing. - */ - private function getService(): KeyCreationService - { - return new KeyCreationService($this->repository, $this->encrypter); - } -}