Skip to content

Commit 4479d3b

Browse files
committed
Improved logic for handling permissions on API routes.
Still only partially implemented, however this method will allow the inclusion of data that is granted with servers (such as viewing more about the node, node location, allocations, etc) while still limiting someone from doing `?include=node.servers` and listing all servers when they don’t have list-servers as a permission.
1 parent db4df2b commit 4479d3b

16 files changed

+296
-29
lines changed

app/Http/Controllers/API/Admin/ServerController.php

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function index(Request $request)
4545

4646
return Fractal::create()
4747
->collection($servers)
48-
->transformWith(new ServerTransformer)
48+
->transformWith(new ServerTransformer($request))
4949
->paginateWith(new IlluminatePaginatorAdapter($servers))
5050
->withResourceName('server')
5151
->toArray();
@@ -62,20 +62,11 @@ public function view(Request $request, $id)
6262
$server = Server::findOrFail($id);
6363
$fractal = Fractal::create()->item($server);
6464

65-
// dd($request->user()->can('view-node', $request->apiKey()));
66-
67-
// Have the api key model return a list of includes that would be allowed
68-
// given the permissions they have aleady been granted?
69-
//
70-
// If someone has 'view-node' they would then be able to use ->parseIncludes(['*.node.*']);
71-
// How that logic will work is beyond me currently, but should keep things
72-
// fairly clean?
73-
7465
if ($request->input('include')) {
7566
$fractal->parseIncludes(explode(',', $request->input('include')));
7667
}
7768

78-
return $fractal->transformWith(new ServerTransformer)
69+
return $fractal->transformWith(new ServerTransformer($request))
7970
->withResourceName('server')
8071
->toArray();
8172
}

app/Models/APIKey.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828

2929
class APIKey extends Model
3030
{
31+
/**
32+
* Public key defined length used in verification methods.
33+
*
34+
* @var int
35+
*/
36+
const PUBLIC_KEY_LEN = 16;
37+
3138
/**
3239
* The table associated with the model.
3340
*

app/Policies/APIKeyPolicy.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ class APIKeyPolicy
4242
*/
4343
private function checkPermission(User $user, Key $key, $permission)
4444
{
45-
$permissions = Cache::remember('APIKeyPolicy.' . $user->uuid . $key->public, Carbon::now()->addSeconds(5), function () use ($key) {
45+
// We don't tag this cache key with the user uuid because the key is already unique,
46+
// and multiple users are not defiend for a single key.
47+
$permissions = Cache::remember('APIKeyPolicy.' . $key->public, Carbon::now()->addSeconds(5), function () use ($key) {
4648
return $key->permissions()->get()->transform(function ($item) {
4749
return $item->permission;
4850
})->values();

app/Providers/MacroServiceProvider.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
namespace Pterodactyl\Providers;
2626

2727
use File;
28+
use Cache;
29+
use Carbon;
2830
use Request;
31+
use Pterodactyl\Models\APIKey;
2932
use Illuminate\Support\ServiceProvider;
3033

3134
class MacroServiceProvider extends ServiceProvider
@@ -57,11 +60,27 @@ public function boot()
5760

5861
$parts = explode('.', Request::bearerToken());
5962

60-
if (count($parts) === 2) {
61-
return \Pterodactyl\Models\APIKey::where('public', $parts[0])->first();
63+
if (count($parts) === 2 && strlen($parts[0]) === APIKey::PUBLIC_KEY_LEN) {
64+
// Because the key itself isn't changing frequently, we simply cache this for
65+
// 15 minutes to speed up the API and keep requests flowing.
66+
return Cache::tags([
67+
'ApiKeyMacro',
68+
'ApiKeyMacro:Key:' . $parts[0],
69+
])->remember('ApiKeyMacro.' . $parts[0], Carbon::now()->addMinutes(15), function() use ($parts) {
70+
return APIKey::where('public', $parts[0])->first();
71+
});
6272
}
6373

6474
return false;
6575
});
76+
77+
Request::macro('apiKeyHasPermission', function($permission) {
78+
$key = Request::apiKey();
79+
if (! $key) {
80+
return false;
81+
}
82+
83+
return Request::user()->can($permission, $key);
84+
});
6685
}
6786
}

app/Transformers/Admin/AllocationTransformer.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
namespace Pterodactyl\Transformers\Admin;
2626

27+
use Illuminate\Http\Request;
2728
use Pterodactyl\Models\Allocation;
2829
use League\Fractal\TransformerAbstract;
2930

@@ -37,13 +38,26 @@ class AllocationTransformer extends TransformerAbstract
3738
protected $filter;
3839

3940
/**
40-
* Transformer constructor.
41+
* The Illuminate Request object if provided.
4142
*
42-
* @param bool|string $filter
43+
* @var \Illuminate\Http\Request|bool
44+
*/
45+
protected $request;
46+
47+
/**
48+
* Setup request object for transformer.
49+
*
50+
* @param \Illuminate\Http\Request|bool $request
51+
* @param bool $filter
4352
* @return void
4453
*/
45-
public function __construct($filter = false)
54+
public function __construct($request = false, $filter = false)
4655
{
56+
if (! $request instanceof Request && $request !== false) {
57+
throw new DisplayException('Request passed to constructor must be of type Request or false.');
58+
}
59+
60+
$this->request = $request;
4761
$this->filter = $filter;
4862
}
4963

app/Transformers/Admin/LocationTransformer.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
namespace Pterodactyl\Transformers\Admin;
2626

27+
use Illuminate\Http\Request;
2728
use Pterodactyl\Models\Location;
2829
use League\Fractal\TransformerAbstract;
2930

@@ -39,6 +40,28 @@ class LocationTransformer extends TransformerAbstract
3940
'servers',
4041
];
4142

43+
/**
44+
* The Illuminate Request object if provided.
45+
*
46+
* @var \Illuminate\Http\Request|bool
47+
*/
48+
protected $request;
49+
50+
/**
51+
* Setup request object for transformer.
52+
*
53+
* @param \Illuminate\Http\Request|bool $request
54+
* @return void
55+
*/
56+
public function __construct($request = false)
57+
{
58+
if (! $request instanceof Request && $request !== false) {
59+
throw new DisplayException('Request passed to constructor must be of type Request or false.');
60+
}
61+
62+
$this->request = $request;
63+
}
64+
4265
/**
4366
* Return a generic transformed pack array.
4467
*

app/Transformers/Admin/NodeTransformer.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
namespace Pterodactyl\Transformers\Admin;
2626

27+
use Illuminate\Http\Request;
2728
use Pterodactyl\Models\Node;
2829
use League\Fractal\TransformerAbstract;
2930

@@ -40,6 +41,28 @@ class NodeTransformer extends TransformerAbstract
4041
'servers',
4142
];
4243

44+
/**
45+
* The Illuminate Request object if provided.
46+
*
47+
* @var \Illuminate\Http\Request|bool
48+
*/
49+
protected $request;
50+
51+
/**
52+
* Setup request object for transformer.
53+
*
54+
* @param \Illuminate\Http\Request|bool $request
55+
* @return void
56+
*/
57+
public function __construct($request = false)
58+
{
59+
if (! $request instanceof Request && $request !== false) {
60+
throw new DisplayException('Request passed to constructor must be of type Request or false.');
61+
}
62+
63+
$this->request = $request;
64+
}
65+
4366
/**
4467
* Return a generic transformed pack array.
4568
*
@@ -77,6 +100,10 @@ public function includeLocation(Node $node)
77100
*/
78101
public function includeServers(Node $node)
79102
{
103+
if ($this->request && ! $this->request->apiKeyHasPermission('list-servers')) {
104+
return;
105+
}
106+
80107
return $this->collection($node->servers, new ServerTransformer, 'server');
81108
}
82109
}

app/Transformers/Admin/OptionTransformer.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
namespace Pterodactyl\Transformers\Admin;
2626

27+
use Illuminate\Http\Request;
2728
use Pterodactyl\Models\ServiceOption;
2829
use League\Fractal\TransformerAbstract;
2930

@@ -41,6 +42,28 @@ class OptionTransformer extends TransformerAbstract
4142
'variables',
4243
];
4344

45+
/**
46+
* The Illuminate Request object if provided.
47+
*
48+
* @var \Illuminate\Http\Request|bool
49+
*/
50+
protected $request;
51+
52+
/**
53+
* Setup request object for transformer.
54+
*
55+
* @param \Illuminate\Http\Request|bool $request
56+
* @return void
57+
*/
58+
public function __construct($request = false)
59+
{
60+
if (! $request instanceof Request && $request !== false) {
61+
throw new DisplayException('Request passed to constructor must be of type Request or false.');
62+
}
63+
64+
$this->request = $request;
65+
}
66+
4467
/**
4568
* Return a generic transformed service option array.
4669
*

app/Transformers/Admin/PackTransformer.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
namespace Pterodactyl\Transformers\Admin;
2626

27+
use Illuminate\Http\Request;
2728
use Pterodactyl\Models\Pack;
2829
use League\Fractal\TransformerAbstract;
2930

@@ -39,6 +40,28 @@ class PackTransformer extends TransformerAbstract
3940
'servers',
4041
];
4142

43+
/**
44+
* The Illuminate Request object if provided.
45+
*
46+
* @var \Illuminate\Http\Request|bool
47+
*/
48+
protected $request;
49+
50+
/**
51+
* Setup request object for transformer.
52+
*
53+
* @param \Illuminate\Http\Request|bool $request
54+
* @return void
55+
*/
56+
public function __construct($request = false)
57+
{
58+
if (! $request instanceof Request && $request !== false) {
59+
throw new DisplayException('Request passed to constructor must be of type Request or false.');
60+
}
61+
62+
$this->request = $request;
63+
}
64+
4265
/**
4366
* Return a generic transformed pack array.
4467
*

0 commit comments

Comments
 (0)