Skip to content

Commit 61e9771

Browse files
committed
Code cleanup for subuser API endpoints; closes pterodactyl#2247
1 parent 57bb652 commit 61e9771

File tree

9 files changed

+94
-104
lines changed

9 files changed

+94
-104
lines changed

app/Exceptions/Handler.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ public static function convertToArray(Throwable $exception, array $override = []
213213
'detail' => 'An error was encountered while processing this request.',
214214
];
215215

216+
if ($exception instanceof ModelNotFoundException || $exception->getPrevious() instanceof ModelNotFoundException) {
217+
// Show a nicer error message compared to the standard "No query results for model"
218+
// response that is normally returned. If we are in debug mode this will get overwritten
219+
// with a more specific error message to help narrow down things.
220+
$error['detail'] = 'The requested resource could not be found on the server.';
221+
}
222+
216223
if (config('app.debug')) {
217224
$error = array_merge($error, [
218225
'detail' => $exception->getMessage(),

app/Http/Controllers/Api/Client/Servers/SubuserController.php

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
44

55
use Illuminate\Http\Request;
6+
use Pterodactyl\Models\User;
67
use Pterodactyl\Models\Server;
8+
use Pterodactyl\Models\Subuser;
79
use Illuminate\Http\JsonResponse;
810
use Pterodactyl\Models\Permission;
911
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
@@ -57,6 +59,21 @@ public function index(GetSubuserRequest $request, Server $server)
5759
->toArray();
5860
}
5961

62+
/**
63+
* Returns a single subuser associated with this server instance.
64+
*
65+
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest $request
66+
* @return array
67+
*/
68+
public function view(GetSubuserRequest $request)
69+
{
70+
$subuser = $request->attributes->get('subuser');
71+
72+
return $this->fractal->item($subuser)
73+
->transformWith($this->getTransformer(SubuserTransformer::class))
74+
->toArray();
75+
}
76+
6077
/**
6178
* Create a new subuser for the given server.
6279
*
@@ -84,15 +101,16 @@ public function store(StoreSubuserRequest $request, Server $server)
84101
* Update a given subuser in the system for the server.
85102
*
86103
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest $request
87-
* @param \Pterodactyl\Models\Server $server
88104
* @return array
89105
*
90106
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
91107
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
92108
*/
93-
public function update(UpdateSubuserRequest $request, Server $server): array
109+
public function update(UpdateSubuserRequest $request): array
94110
{
95-
$subuser = $request->endpointSubuser();
111+
/** @var \Pterodactyl\Models\Subuser $subuser */
112+
$subuser = $request->attributes->get('subuser');
113+
96114
$this->repository->update($subuser->id, [
97115
'permissions' => $this->getDefaultPermissions($request),
98116
]);
@@ -106,14 +124,16 @@ public function update(UpdateSubuserRequest $request, Server $server): array
106124
* Removes a subusers from a server's assignment.
107125
*
108126
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest $request
109-
* @param \Pterodactyl\Models\Server $server
110127
* @return \Illuminate\Http\JsonResponse
111128
*/
112-
public function delete(DeleteSubuserRequest $request, Server $server)
129+
public function delete(DeleteSubuserRequest $request)
113130
{
114-
$this->repository->delete($request->endpointSubuser()->id);
131+
/** @var \Pterodactyl\Models\Subuser $subuser */
132+
$subuser = $request->attributes->get('subuser');
133+
134+
$this->repository->delete($subuser->id);
115135

116-
return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT);
136+
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
117137
}
118138

119139
/**
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Middleware\Api\Client\Server;
4+
5+
use Closure;
6+
use Exception;
7+
use Illuminate\Http\Request;
8+
9+
class SubuserBelongsToServer
10+
{
11+
/**
12+
* Ensure that the user being accessed in the request is a user that is currently assigned
13+
* as a subuser for this server instance. We'll let the requests themselves handle wether or
14+
* not the user making the request can actually modify or delete the subuser record.
15+
*
16+
* @param \Illuminate\Http\Request $request
17+
* @param \Closure $next
18+
*
19+
* @return mixed
20+
*/
21+
public function handle(Request $request, Closure $next)
22+
{
23+
/** @var \Pterodactyl\Models\Server $server */
24+
$server = $request->route()->parameter('server');
25+
/** @var \Pterodactyl\Models\User $user */
26+
$user = $request->route()->parameter('user');
27+
28+
// Don't do anything if there isn't a user present in the request.
29+
if (is_null($user)) {
30+
return $next($request);
31+
}
32+
33+
$request->attributes->set('subuser', $server->subusers()->where('user_id', $user->id)->firstOrFail());
34+
35+
return $next($request);
36+
}
37+
}

app/Http/Middleware/Api/Client/SubstituteClientApiBindings.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Pterodactyl\Http\Middleware\Api\Client;
44

55
use Closure;
6+
use Pterodactyl\Models\User;
67
use Pterodactyl\Models\Backup;
78
use Pterodactyl\Models\Database;
89
use Illuminate\Container\Container;
@@ -52,6 +53,10 @@ public function handle($request, Closure $next)
5253
return Backup::query()->where('uuid', $value)->firstOrFail();
5354
});
5455

56+
$this->router->model('user', User::class, function ($value) {
57+
return User::query()->where('uuid', $value)->firstOrFail();
58+
});
59+
5560
return parent::handle($request, $next);
5661
}
5762
}

app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php

Lines changed: 11 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
44

55
use Illuminate\Http\Request;
6-
use Pterodactyl\Models\Server;
6+
use Pterodactyl\Models\User;
77
use Pterodactyl\Exceptions\Http\HttpForbiddenException;
8-
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
98
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
10-
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
11-
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
9+
use Pterodactyl\Services\Servers\GetUserPermissionsService;
1210

1311
abstract class SubuserRequest extends ClientApiRequest
1412
{
@@ -30,10 +28,10 @@ public function authorize(): bool
3028
return false;
3129
}
3230

33-
// If there is a subuser present in the URL, validate that it is not the same as the
34-
// current request user. You're not allowed to modify yourself.
35-
if ($this->route()->hasParameter('subuser')) {
36-
if ($this->endpointSubuser()->user_id === $this->user()->id) {
31+
$user = $this->route()->parameter('user');
32+
// Don't allow a user to edit themselves on the server.
33+
if ($user instanceof User) {
34+
if ($user->uuid === $this->user()->uuid) {
3735
return false;
3836
}
3937
}
@@ -71,68 +69,14 @@ protected function validatePermissionsCanBeAssigned(array $permissions)
7169
// Otherwise, get the current subuser's permission set, and ensure that the
7270
// permissions they are trying to assign are not _more_ than the ones they
7371
// already have.
74-
if (count(array_diff($permissions, $this->currentUserPermissions())) > 0) {
72+
/** @var \Pterodactyl\Models\Subuser|null $subuser */
73+
/** @var \Pterodactyl\Services\Servers\GetUserPermissionsService $service */
74+
$service = $this->container->make(GetUserPermissionsService::class);
75+
76+
if (count(array_diff($permissions, $service->handle($server, $user))) > 0) {
7577
throw new HttpForbiddenException(
7678
'Cannot assign permissions to a subuser that your account does not actively possess.'
7779
);
7880
}
7981
}
80-
81-
/**
82-
* Returns the currently authenticated user's permissions.
83-
*
84-
* @return array
85-
*
86-
* @throws \Illuminate\Contracts\Container\BindingResolutionException
87-
*/
88-
public function currentUserPermissions(): array
89-
{
90-
/** @var \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository */
91-
$repository = $this->container->make(SubuserRepository::class);
92-
93-
/* @var \Pterodactyl\Models\Subuser $model */
94-
try {
95-
$model = $repository->findFirstWhere([
96-
['server_id', $this->route()->parameter('server')->id],
97-
['user_id', $this->user()->id],
98-
]);
99-
} catch (RecordNotFoundException $exception) {
100-
return [];
101-
}
102-
103-
return $model->permissions;
104-
}
105-
106-
/**
107-
* Return the subuser model for the given request which can then be validated. If
108-
* required request parameters are missing a 404 error will be returned, otherwise
109-
* a model exception will be returned if the model is not found.
110-
*
111-
* This returns the subuser based on the endpoint being hit, not the actual subuser
112-
* for the account making the request.
113-
*
114-
* @return \Pterodactyl\Models\Subuser
115-
*
116-
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
117-
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
118-
* @throws \Illuminate\Contracts\Container\BindingResolutionException
119-
*/
120-
public function endpointSubuser()
121-
{
122-
/** @var \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository */
123-
$repository = $this->container->make(SubuserRepository::class);
124-
125-
$parameters = $this->route()->parameters();
126-
if (
127-
! isset($parameters['server'], $parameters['server'])
128-
|| ! is_string($parameters['subuser'])
129-
|| ! $parameters['server'] instanceof Server
130-
) {
131-
throw new NotFoundHttpException;
132-
}
133-
134-
return $this->model ?: $this->model = $repository->getUserForServer(
135-
$parameters['server']->id, $parameters['subuser']
136-
);
137-
}
13882
}

app/Models/Server.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
* @property \Carbon\Carbon $updated_at
3939
*
4040
* @property \Pterodactyl\Models\User $user
41-
* @property \Pterodactyl\Models\User[]|\Illuminate\Database\Eloquent\Collection $subusers
41+
* @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subusers
4242
* @property \Pterodactyl\Models\Allocation $allocation
4343
* @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations
4444
* @property \Pterodactyl\Models\Pack|null $pack

app/Repositories/Eloquent/SubuserRepository.php

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,6 @@ public function model()
1818
return Subuser::class;
1919
}
2020

21-
/**
22-
* Returns a subuser model for the given user and server combination. If no record
23-
* exists an exception will be thrown.
24-
*
25-
* @param int $server
26-
* @param string $uuid
27-
* @return \Pterodactyl\Models\Subuser
28-
*
29-
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
30-
*/
31-
public function getUserForServer(int $server, string $uuid): Subuser
32-
{
33-
/** @var \Pterodactyl\Models\Subuser $model */
34-
$model = $this->getBuilder()
35-
->with('server', 'user')
36-
->select('subusers.*')
37-
->join('users', 'users.id', '=', 'subusers.user_id')
38-
->where('subusers.server_id', $server)
39-
->where('users.uuid', $uuid)
40-
->firstOrFail();
41-
42-
return $model;
43-
}
44-
4521
/**
4622
* Return a subuser with the associated server relationship.
4723
*

app/Services/Servers/GetUserPermissionsService.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function handle(Server $server, User $user)
3030
}
3131

3232
/** @var \Pterodactyl\Models\Subuser|null $subuserPermissions */
33-
$subuserPermissions = $server->subusers->where('user_id', $user->id)->first();
33+
$subuserPermissions = $server->subusers()->where('user_id', $user->id)->first();
3434

3535
return $subuserPermissions ? $subuserPermissions->permissions : [];
3636
}

routes/api-client.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Illuminate\Support\Facades\Route;
4+
use Pterodactyl\Http\Middleware\Api\Client\Server\SubuserBelongsToServer;
45
use Pterodactyl\Http\Middleware\Api\Client\Server\AuthenticateServerAccess;
56
use Pterodactyl\Http\Middleware\Api\Client\Server\AllocationBelongsToServer;
67

@@ -84,12 +85,12 @@
8485
Route::delete('/allocations/{allocation}', 'Servers\NetworkAllocationController@delete');
8586
});
8687

87-
Route::group(['prefix' => '/users'], function () {
88+
Route::group(['prefix' => '/users', 'middleware' => [SubuserBelongsToServer::class]], function () {
8889
Route::get('/', 'Servers\SubuserController@index');
8990
Route::post('/', 'Servers\SubuserController@store');
90-
Route::get('/{subuser}', 'Servers\SubuserController@view');
91-
Route::post('/{subuser}', 'Servers\SubuserController@update');
92-
Route::delete('/{subuser}', 'Servers\SubuserController@delete');
91+
Route::get('/{user}', 'Servers\SubuserController@view');
92+
Route::post('/{user}', 'Servers\SubuserController@update');
93+
Route::delete('/{user}', 'Servers\SubuserController@delete');
9394
});
9495

9596
Route::group(['prefix' => '/backups'], function () {

0 commit comments

Comments
 (0)