From 698c121e1157bbde7700ecec07ddf30f5cb1a551 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 19 Nov 2017 16:30:00 -0600 Subject: [PATCH] First round of API additions --- .../Repository/RepositoryInterface.php | 3 +- .../API/Admin/Users/UserController.php | 73 ++++++++++++ app/Models/User.php | 2 +- .../Eloquent/EloquentRepository.php | 10 +- app/Transformers/Admin/UserTransformer.php | 59 +++------ app/Transformers/ApiTransformer.php | 39 ++++++ routes/api-admin.php | 112 +++--------------- spec/admin/swagger.yaml | 73 ++++++++++++ 8 files changed, 229 insertions(+), 142 deletions(-) create mode 100644 app/Http/Controllers/API/Admin/Users/UserController.php create mode 100644 app/Transformers/ApiTransformer.php create mode 100644 spec/admin/swagger.yaml diff --git a/app/Contracts/Repository/RepositoryInterface.php b/app/Contracts/Repository/RepositoryInterface.php index f31e852d8..cfe5fde62 100644 --- a/app/Contracts/Repository/RepositoryInterface.php +++ b/app/Contracts/Repository/RepositoryInterface.php @@ -168,9 +168,10 @@ interface RepositoryInterface /** * Return all records from the model. * + * @param null $paginate * @return mixed */ - public function all(); + public function all($paginate = null); /** * Insert a single or multiple records into the database at once skipping diff --git a/app/Http/Controllers/API/Admin/Users/UserController.php b/app/Http/Controllers/API/Admin/Users/UserController.php new file mode 100644 index 000000000..46243b19c --- /dev/null +++ b/app/Http/Controllers/API/Admin/Users/UserController.php @@ -0,0 +1,73 @@ +fractal = $fractal; + $this->repository = $repository; + $this->config = $config; + } + + /** + * Handle request to list all users on the panel. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function index(Request $request) + { + $users = $this->repository->all($this->config->get('pterodactyl.paginate.api.users')); + + $fractal = $this->fractal->collection($users) + ->transformWith(new UserTransformer($request)) + ->withResourceName('user') + ->paginateWith(new IlluminatePaginatorAdapter($users)); + + if ($this->config->get('pterodactyl.api.include_on_list') && $request->input('include')) { + $fractal->parseIncludes(explode(',', $request->input('include'))); + } + + return $fractal->toArray(); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 39e4a0a03..f9291f469 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -89,7 +89,7 @@ class User extends Model implements * * @var array */ - protected $hidden = ['password', 'remember_token', 'totp_secret']; + protected $hidden = ['password', 'remember_token', 'totp_secret', 'totp_authenticated_at']; /** * Parameters for search querying. diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index d94cd5cac..f9124a718 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -184,14 +184,20 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf /** * {@inheritdoc} */ - public function all() + public function all($paginate = null) { + Assert::nullOrIntegerish($paginate, 'First argument passed to all must be null or integer, received %s.'); + $instance = $this->getBuilder(); if (is_subclass_of(get_called_class(), SearchableInterface::class)) { $instance = $instance->search($this->searchTerm); } - return $instance->get($this->getColumns()); + if (is_null($paginate)) { + return $instance->get($this->getColumns()); + } + + return $instance->paginate($paginate, $this->getColumns()); } /** diff --git a/app/Transformers/Admin/UserTransformer.php b/app/Transformers/Admin/UserTransformer.php index 323e72c4c..90547948a 100644 --- a/app/Transformers/Admin/UserTransformer.php +++ b/app/Transformers/Admin/UserTransformer.php @@ -1,57 +1,37 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Transformers\Admin; use Illuminate\Http\Request; use Pterodactyl\Models\User; -use League\Fractal\TransformerAbstract; +use Pterodactyl\Transformers\ApiTransformer; -class UserTransformer extends TransformerAbstract +class UserTransformer extends ApiTransformer { /** * List of resources that can be included. * * @var array */ - protected $availableIncludes = [ - 'access', - 'servers', - ]; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; + protected $availableIncludes = ['servers']; /** * Setup request object for transformer. * - * @param \Illuminate\Http\Request|bool $request + * @param \Illuminate\Http\Request $request */ - public function __construct($request = false) + public function __construct(Request $request) { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - $this->request = $request; } /** * Return a generic transformed subuser array. * + * @param \Pterodactyl\Models\User $user * @return array */ - public function transform(User $user) + public function transform(User $user): array { return $user->toArray(); } @@ -59,28 +39,21 @@ class UserTransformer extends TransformerAbstract /** * Return the servers associated with this user. * - * @return \Leauge\Fractal\Resource\Collection + * @param \Pterodactyl\Models\User $user + * @return bool|\League\Fractal\Resource\Collection + * + * @throws \Pterodactyl\Exceptions\PterodactylException */ public function includeServers(User $user) { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; + if ($this->authorize('server-list')) { + return false; } - return $this->collection($user->servers, new ServerTransformer($this->request), 'server'); - } - - /** - * Return the servers that this user can access. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeAccess(User $user) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; + if (! $user->relationLoaded('servers')) { + $user->load('servers'); } - return $this->collection($user->access()->get(), new ServerTransformer($this->request), 'server'); + return $this->collection($user->getRelation('servers'), new ServerTransformer($this->request), 'server'); } } diff --git a/app/Transformers/ApiTransformer.php b/app/Transformers/ApiTransformer.php new file mode 100644 index 000000000..d2334a079 --- /dev/null +++ b/app/Transformers/ApiTransformer.php @@ -0,0 +1,39 @@ +request->attributes->get('api_key'); + if (! $model->relationLoaded('permissions')) { + throw new PterodactylException('Permissions must be loaded onto a model before passing to transformer authorize function.'); + } + + $count = $model->getRelation('permissions')->filter(function ($model) use ($permission) { + return $model->permission === $permission; + })->count(); + + return $count > 0; + } +} diff --git a/routes/api-admin.php b/routes/api-admin.php index a36adf3b4..abd570ec9 100644 --- a/routes/api-admin.php +++ b/routes/api-admin.php @@ -1,97 +1,19 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -//Route::get('/', 'CoreController@index'); -// -///* -//|-------------------------------------------------------------------------- -//| Server Controller Routes -//|-------------------------------------------------------------------------- -//| -//| Endpoint: /api/admin/servers -//| -//*/ -//Route::group(['prefix' => '/servers'], function () { -// Route::get('/', 'ServerController@index'); -// Route::get('/{id}', 'ServerController@view'); -// -// Route::post('/', 'ServerController@store'); -// -// Route::put('/{id}/details', 'ServerController@details'); -// Route::put('/{id}/container', 'ServerController@container'); -// Route::put('/{id}/build', 'ServerController@build'); -// Route::put('/{id}/startup', 'ServerController@startup'); -// -// Route::patch('/{id}/install', 'ServerController@install'); -// Route::patch('/{id}/rebuild', 'ServerController@rebuild'); -// Route::patch('/{id}/suspend', 'ServerController@suspend'); -// -// Route::delete('/{id}', 'ServerController@delete'); -//}); -// -///* -//|-------------------------------------------------------------------------- -//| Location Controller Routes -//|-------------------------------------------------------------------------- -//| -//| Endpoint: /api/admin/locations -//| -//*/ -//Route::group(['prefix' => '/locations'], function () { -// Route::get('/', 'LocationController@index'); -//}); -// -///* -//|-------------------------------------------------------------------------- -//| Node Controller Routes -//|-------------------------------------------------------------------------- -//| -//| Endpoint: /api/admin/nodes -//| -//*/ -//Route::group(['prefix' => '/nodes'], function () { -// Route::get('/', 'NodeController@index'); -// Route::get('/{id}', 'NodeController@view'); -// Route::get('/{id}/config', 'NodeController@viewConfig'); -// -// Route::post('/', 'NodeController@store'); -// -// Route::delete('/{id}', 'NodeController@delete'); -//}); -// -///* -//|-------------------------------------------------------------------------- -//| User Controller Routes -//|-------------------------------------------------------------------------- -//| -//| Endpoint: /api/admin/users -//| -//*/ -//Route::group(['prefix' => '/users'], function () { -// Route::get('/', 'UserController@index'); -// Route::get('/{id}', 'UserController@view'); -// -// Route::post('/', 'UserController@store'); -// -// Route::put('/{id}', 'UserController@update'); -// -// Route::delete('/{id}', 'UserController@delete'); -//}); -// -///* -//|-------------------------------------------------------------------------- -//| Service Controller Routes -//|-------------------------------------------------------------------------- -//| -//| Endpoint: /api/admin/services -//| -//*/ -//Route::group(['prefix' => '/services'], function () { -// Route::get('/', 'ServiceController@index'); -// Route::get('/{id}', 'ServiceController@view'); -//}); +|-------------------------------------------------------------------------- +| User Controller Routes +|-------------------------------------------------------------------------- +| +| Endpoint: /api/admin/users +| +*/ +Route::group(['prefix' => '/users'], function () { + Route::get('/', 'Users\UserController@index')->name('api.admin.user.list'); + Route::get('/{id}', 'Users\UserController@view'); + + Route::post('/', 'Users\UserController@store'); + Route::put('/{id}', 'Users\UserController@update'); + + Route::delete('/{id}', 'Users\UserController@delete'); +}); diff --git a/spec/admin/swagger.yaml b/spec/admin/swagger.yaml new file mode 100644 index 000000000..d05039970 --- /dev/null +++ b/spec/admin/swagger.yaml @@ -0,0 +1,73 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Pterodactyl Admin API Reference + description: Pterodactyl Panel API Documentation + contact: + name: Dane Everitt + url: https://pterodactyl.io + email: support@pterodactyl.io + license: + name: MIT +host: example.com +basePath: /api/admin +schemes: + - http + - https +consumes: + - application/vnd.pterodactyl.v1+json +produces: + - application/json +paths: + /users: + get: + description: | + Returns all users that exist on the Panel. + operationId: findUsers + responses: + "200": + description: OK + schema: + type: object + required: ["data"] + properties: + data: + type: array + items: + $ref: '#/definitions/User' + properties: + id: + type: integer + attributes: + type: object +definitions: + User: + allOf: + - required: + - email + - username + - uuid + properties: + external_id: + type: string + uuid: + type: string + email: + type: string + username: + type: string + name_first: + type: string + name_last: + type: string + language: + type: string + root_admin: + type: boolean + use_totp: + type: boolean + updated_at: + type: string + created_at: + type: string +