From cef3e4ced4cb1968780163c786dc3e20136923e4 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 27 Feb 2018 21:28:43 -0600 Subject: [PATCH] Add base routes for managing servers as a client --- .../Application/ApplicationApiController.php | 8 +- .../Api/Client/ClientApiController.php | 15 ++-- .../Api/Client/ClientController.php | 37 +++++++- .../Api/Client/Servers/ServerController.php | 25 ++++++ app/Http/Kernel.php | 3 +- .../Middleware/Api/ApiSubstituteBindings.php | 9 ++ .../Client/SubstituteClientApiBindings.php | 39 +++++++++ .../Requests/Api/Client/ClientApiRequest.php | 19 +++++ .../Requests/Api/Client/GetServersRequest.php | 14 +++ .../Api/Client/Servers/GetServerRequest.php | 31 +++++++ .../Api/Client/BaseClientTransformer.php | 38 ++++++++- .../Api/Client/ServerTransformer.php | 41 +++++++++ .../User/AllocationTransformer.php | 47 ---------- app/Transformers/User/OverviewTransformer.php | 35 -------- app/Transformers/User/ServerTransformer.php | 85 ------------------- app/Transformers/User/StatsTransformer.php | 48 ----------- app/Transformers/User/SubuserTransformer.php | 32 ------- routes/api-client.php | 6 +- 18 files changed, 262 insertions(+), 270 deletions(-) create mode 100644 app/Http/Controllers/Api/Client/Servers/ServerController.php create mode 100644 app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php create mode 100644 app/Http/Requests/Api/Client/ClientApiRequest.php create mode 100644 app/Http/Requests/Api/Client/GetServersRequest.php create mode 100644 app/Http/Requests/Api/Client/Servers/GetServerRequest.php create mode 100644 app/Transformers/Api/Client/ServerTransformer.php delete mode 100644 app/Transformers/User/AllocationTransformer.php delete mode 100644 app/Transformers/User/OverviewTransformer.php delete mode 100644 app/Transformers/User/ServerTransformer.php delete mode 100644 app/Transformers/User/StatsTransformer.php delete mode 100644 app/Transformers/User/SubuserTransformer.php diff --git a/app/Http/Controllers/Api/Application/ApplicationApiController.php b/app/Http/Controllers/Api/Application/ApplicationApiController.php index b311c110e..bdd5f9e7b 100644 --- a/app/Http/Controllers/Api/Application/ApplicationApiController.php +++ b/app/Http/Controllers/Api/Application/ApplicationApiController.php @@ -3,12 +3,12 @@ namespace Pterodactyl\Http\Controllers\Api\Application; use Illuminate\Http\Request; +use Webmozart\Assert\Assert; use Illuminate\Http\Response; use Illuminate\Container\Container; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Extensions\Spatie\Fractalistic\Fractal; use Pterodactyl\Transformers\Api\Application\BaseTransformer; -use Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException; abstract class ApplicationApiController extends Controller { @@ -56,8 +56,6 @@ abstract class ApplicationApiController extends Controller * * @param string $abstract * @return \Pterodactyl\Transformers\Api\Application\BaseTransformer - * - * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException */ public function getTransformer(string $abstract) { @@ -65,9 +63,7 @@ abstract class ApplicationApiController extends Controller $transformer = Container::getInstance()->make($abstract); $transformer->setKey($this->request->attributes->get('api_key')); - if (! $transformer instanceof BaseTransformer) { - throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); - } + Assert::isInstanceOf($transformer, BaseTransformer::class); return $transformer; } diff --git a/app/Http/Controllers/Api/Client/ClientApiController.php b/app/Http/Controllers/Api/Client/ClientApiController.php index 2ade63dff..e2d4b3f83 100644 --- a/app/Http/Controllers/Api/Client/ClientApiController.php +++ b/app/Http/Controllers/Api/Client/ClientApiController.php @@ -1,9 +1,11 @@ make($abstract); - $transformer->setKey($this->request->attributes->get('api_key')); + Assert::isInstanceOf($transformer, BaseClientTransformer::class); - if (! $transformer instanceof self) { - throw new InvalidTransformerLevelException('Calls to ' . __METHOD__ . ' must return a transformer that is an instance of ' . __CLASS__); - } + $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 87a5e5f15..d2e1f33a9 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -2,8 +2,43 @@ namespace Pterodactyl\Http\Controllers\Api\Client; -use Pterodactyl\Http\Controllers\Api\Application\ClientApiController; +use Pterodactyl\Models\User; +use Pterodactyl\Transformers\Api\Client\ServerTransformer; +use Pterodactyl\Http\Requests\Api\Client\GetServersRequest; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class ClientController extends ClientApiController { + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + private $repository; + + /** + * ClientController constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + */ + public function __construct(ServerRepositoryInterface $repository) + { + parent::__construct(); + + $this->repository = $repository; + } + + /** + * Return all of the servers available to the client making the API + * request, including servers the user has access to as a subuser. + * + * @param \Pterodactyl\Http\Requests\Api\Client\GetServersRequest $request + * @return array + */ + public function index(GetServersRequest $request): array + { + $servers = $this->repository->filterUserAccessServers($request->user(), User::FILTER_LEVEL_SUBUSER); + + return $this->fractal->collection($servers) + ->transformWith($this->getTransformer(ServerTransformer::class)) + ->toArray(); + } } diff --git a/app/Http/Controllers/Api/Client/Servers/ServerController.php b/app/Http/Controllers/Api/Client/Servers/ServerController.php new file mode 100644 index 000000000..ce4502e3a --- /dev/null +++ b/app/Http/Controllers/Api/Client/Servers/ServerController.php @@ -0,0 +1,25 @@ +fractal->item($request->getModel(Server::class)) + ->transformWith($this->getTransformer(ServerTransformer::class)) + ->toArray(); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 5e8012015..b6d44530e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -34,6 +34,7 @@ use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; +use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; use Pterodactyl\Http\Middleware\DaemonAuthenticate as OldDaemonAuthenticate; @@ -78,7 +79,7 @@ class Kernel extends HttpKernel ], 'client-api' => [ 'throttle:60,1', - ApiSubstituteBindings::class, + SubstituteClientApiBindings::class, SetSessionDriver::class, 'api..key:' . ApiKey::TYPE_ACCOUNT, AuthenticateIPAccess::class, diff --git a/app/Http/Middleware/Api/ApiSubstituteBindings.php b/app/Http/Middleware/Api/ApiSubstituteBindings.php index a56a3426f..94af9b1d4 100644 --- a/app/Http/Middleware/Api/ApiSubstituteBindings.php +++ b/app/Http/Middleware/Api/ApiSubstituteBindings.php @@ -32,6 +32,11 @@ class ApiSubstituteBindings extends SubstituteBindings '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. @@ -45,6 +50,10 @@ class ApiSubstituteBindings extends SubstituteBindings $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); }); diff --git a/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php new file mode 100644 index 000000000..f8a35fdd8 --- /dev/null +++ b/app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php @@ -0,0 +1,39 @@ +router->bind('server', function ($value) use ($request) { + try { + return Container::getInstance()->make(ServerRepositoryInterface::class)->findFirstWhere([ + ['uuidShort', '=', $value], + ]); + } catch (RecordNotFoundException $ex) { + $request->attributes->set('is_missing_model', true); + + return null; + } + }); + + return parent::handle($request, $next); + } +} diff --git a/app/Http/Requests/Api/Client/ClientApiRequest.php b/app/Http/Requests/Api/Client/ClientApiRequest.php new file mode 100644 index 000000000..ed63ccbf0 --- /dev/null +++ b/app/Http/Requests/Api/Client/ClientApiRequest.php @@ -0,0 +1,19 @@ +user()->can('view-server', $this->getModel(Server::class)); + } +} diff --git a/app/Transformers/Api/Client/BaseClientTransformer.php b/app/Transformers/Api/Client/BaseClientTransformer.php index 76b618e4d..faa1abbed 100644 --- a/app/Transformers/Api/Client/BaseClientTransformer.php +++ b/app/Transformers/Api/Client/BaseClientTransformer.php @@ -2,23 +2,53 @@ namespace Pterodactyl\Transformers\Api\Client; -use Pterodactyl\Services\Acl\Api\AdminAcl; +use Pterodactyl\Models\User; +use Webmozart\Assert\Assert; +use Pterodactyl\Models\Server; use Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException; use Pterodactyl\Transformers\Api\Application\BaseTransformer as BaseApplicationTransformer; abstract class BaseClientTransformer extends BaseApplicationTransformer { + /** + * @var \Pterodactyl\Models\User + */ + private $user; + + /** + * Return the user model of the user requesting this transformation. + * + * @return \Pterodactyl\Models\User + */ + public function getUser(): User + { + return $this->user; + } + + /** + * Set the user model of the user requesting this transformation. + * + * @param \Pterodactyl\Models\User $user + */ + 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 string $resource + * @param string $ability + * @param \Pterodactyl\Models\Server $server * @return bool */ - protected function authorize(string $resource): bool + protected function authorize(string $ability, Server $server = null): bool { - return AdminAcl::check($this->getKey(), $resource, AdminAcl::READ); + Assert::isInstanceOf($server, Server::class); + + return $this->getUser()->can($ability, [$server]); } /** diff --git a/app/Transformers/Api/Client/ServerTransformer.php b/app/Transformers/Api/Client/ServerTransformer.php new file mode 100644 index 000000000..4f08c208d --- /dev/null +++ b/app/Transformers/Api/Client/ServerTransformer.php @@ -0,0 +1,41 @@ + $this->getKey()->user_id === $server->owner_id, + 'identifier' => $server->uuidShort, + 'uuid' => $server->uuid, + 'name' => $server->name, + 'description' => $server->description, + 'limits' => [ + 'memory' => $server->memory, + 'swap' => $server->swap, + 'disk' => $server->disk, + 'io' => $server->io, + 'cpu' => $server->cpu, + ], + ]; + } +} diff --git a/app/Transformers/User/AllocationTransformer.php b/app/Transformers/User/AllocationTransformer.php deleted file mode 100644 index 8ea0c8f91..000000000 --- a/app/Transformers/User/AllocationTransformer.php +++ /dev/null @@ -1,47 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Transformers\User; - -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Allocation; -use League\Fractal\TransformerAbstract; - -class AllocationTransformer extends TransformerAbstract -{ - /** - * Server eloquent model. - * - * @return \Pterodactyl\Models\Server - */ - protected $server; - - /** - * Setup allocation transformer with access to server data. - */ - public function __construct(Server $server) - { - $this->server = $server; - } - - /** - * Return a generic transformed allocation array. - * - * @return array - */ - public function transform(Allocation $allocation) - { - return [ - 'id' => $allocation->id, - 'ip' => $allocation->alias, - 'port' => $allocation->port, - 'default' => ($allocation->id === $this->server->allocation_id), - ]; - } -} diff --git a/app/Transformers/User/OverviewTransformer.php b/app/Transformers/User/OverviewTransformer.php deleted file mode 100644 index b59990766..000000000 --- a/app/Transformers/User/OverviewTransformer.php +++ /dev/null @@ -1,35 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Transformers\User; - -use Pterodactyl\Models\Server; -use League\Fractal\TransformerAbstract; - -class OverviewTransformer extends TransformerAbstract -{ - /** - * Return a generic transformed server array. - * - * @return array - */ - public function transform(Server $server) - { - return [ - 'id' => $server->uuidShort, - 'uuid' => $server->uuid, - 'name' => $server->name, - 'node' => $server->node->name, - 'ip' => $server->allocation->alias, - 'port' => $server->allocation->port, - 'service' => $server->service->name, - 'option' => $server->option->name, - ]; - } -} diff --git a/app/Transformers/User/ServerTransformer.php b/app/Transformers/User/ServerTransformer.php deleted file mode 100644 index 031ae82f8..000000000 --- a/app/Transformers/User/ServerTransformer.php +++ /dev/null @@ -1,85 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Transformers\User; - -use Pterodactyl\Models\Server; -use League\Fractal\TransformerAbstract; - -class ServerTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = [ - 'allocations', - 'subusers', - 'stats', - ]; - - /** - * Return a generic transformed server array. - * - * @return array - */ - public function transform(Server $server) - { - return [ - 'id' => $server->uuidShort, - 'uuid' => $server->uuid, - 'name' => $server->name, - 'description' => $server->description, - 'node' => $server->node->name, - 'limits' => [ - 'memory' => $server->memory, - 'swap' => $server->swap, - 'disk' => $server->disk, - 'io' => $server->io, - 'cpu' => $server->cpu, - 'oom_disabled' => (bool) $server->oom_disabled, - ], - ]; - } - - /** - * Return a generic array of allocations for this server. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeAllocations(Server $server) - { - $allocations = $server->allocations; - - return $this->collection($allocations, new AllocationTransformer($server), 'allocation'); - } - - /** - * Return a generic array of subusers for this server. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeSubusers(Server $server) - { - $server->load('subusers.permissions', 'subusers.user'); - - return $this->collection($server->subusers, new SubuserTransformer, 'subuser'); - } - - /** - * Return a generic array of allocations for this server. - * - * @return \Leauge\Fractal\Resource\Item - */ - public function includeStats(Server $server) - { - return $this->item($server->guzzleClient(), new StatsTransformer, 'stat'); - } -} diff --git a/app/Transformers/User/StatsTransformer.php b/app/Transformers/User/StatsTransformer.php deleted file mode 100644 index 2a2e1d5ec..000000000 --- a/app/Transformers/User/StatsTransformer.php +++ /dev/null @@ -1,48 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Transformers\User; - -use GuzzleHttp\Client; -use League\Fractal\TransformerAbstract; -use GuzzleHttp\Exception\ConnectException; - -class StatsTransformer extends TransformerAbstract -{ - /** - * Return a generic transformed subuser array. - * - * @return array - */ - public function transform(Client $client) - { - try { - $res = $client->request('GET', '/server', ['http_errors' => false]); - - if ($res->getStatusCode() !== 200) { - return [ - 'error' => 'Error: HttpResponseException. Recieved non-200 HTTP status code from daemon: ' . $res->statusCode(), - ]; - } - - $json = json_decode($res->getBody()); - - return [ - 'id' => 1, - 'status' => $json->status, - 'resources' => $json->proc, - ]; - } catch (ConnectException $ex) { - return [ - 'error' => 'Error: ConnectException. Unable to contact the daemon to request server status.', - 'exception' => (config('app.debug')) ? $ex->getMessage() : null, - ]; - } - } -} diff --git a/app/Transformers/User/SubuserTransformer.php b/app/Transformers/User/SubuserTransformer.php deleted file mode 100644 index faac5965c..000000000 --- a/app/Transformers/User/SubuserTransformer.php +++ /dev/null @@ -1,32 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Transformers\User; - -use Pterodactyl\Models\Subuser; -use League\Fractal\TransformerAbstract; - -class SubuserTransformer extends TransformerAbstract -{ - /** - * Return a generic transformed subuser array. - * - * @return array - */ - public function transform(Subuser $subuser) - { - return [ - 'id' => $subuser->id, - 'username' => $subuser->user->username, - 'email' => $subuser->user->email, - '2fa' => (bool) $subuser->user->use_totp, - 'permissions' => $subuser->permissions->pluck('permission'), - ]; - } -} diff --git a/routes/api-client.php b/routes/api-client.php index fa4cb6c31..b1f4c1b1b 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -21,8 +21,8 @@ Route::get('/', 'ClientController@index')->name('api.client.index'); | */ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateClientAccess::class]], function () { - Route::get('/', 'Server\ServerController@index')->name('api.client.servers.view'); + Route::get('/', 'Servers\ServerController@index')->name('api.client.servers.view'); - Route::post('/command', 'Server\CommandController@index')->name('api.client.servers.command'); - Route::post('/power', 'Server\PowerController@index')->name('api.client.servers.power'); + Route::post('/command', 'Servers\CommandController@index')->name('api.client.servers.command'); + Route::post('/power', 'Servers\PowerController@index')->name('api.client.servers.power'); });