Skip to content

Commit c3b9738

Browse files
committed
Implement application API Keys
1 parent f9fc3f4 commit c3b9738

File tree

13 files changed

+454
-3
lines changed

13 files changed

+454
-3
lines changed

app/Contracts/Repository/ApiKeyRepositoryInterface.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ interface ApiKeyRepositoryInterface extends RepositoryInterface
1515
*/
1616
public function getAccountKeys(User $user): Collection;
1717

18+
/**
19+
* Get all of the application API keys that exist for a specific user.
20+
*
21+
* @param \Pterodactyl\Models\User $user
22+
* @return \Illuminate\Support\Collection
23+
*/
24+
public function getApplicationKeys(User $user): Collection;
25+
1826
/**
1927
* Delete an account API key from the panel for a specific user.
2028
*
@@ -23,4 +31,13 @@ public function getAccountKeys(User $user): Collection;
2331
* @return int
2432
*/
2533
public function deleteAccountKey(User $user, string $identifier): int;
34+
35+
/**
36+
* Delete an application API key from the panel for a specific user.
37+
*
38+
* @param \Pterodactyl\Models\User $user
39+
* @param string $identifier
40+
* @return int
41+
*/
42+
public function deleteApplicationKey(User $user, string $identifier): int;
2643
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Controllers\Admin;
4+
5+
use Illuminate\View\View;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Http\Response;
8+
use Pterodactyl\Models\ApiKey;
9+
use Illuminate\Http\RedirectResponse;
10+
use Prologue\Alerts\AlertsMessageBag;
11+
use Pterodactyl\Services\Acl\Api\AdminAcl;
12+
use Pterodactyl\Http\Controllers\Controller;
13+
use Pterodactyl\Services\Api\KeyCreationService;
14+
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
15+
use Pterodactyl\Http\Requests\Admin\Api\StoreApplicationApiKeyRequest;
16+
17+
class ApplicationApiController extends Controller
18+
{
19+
/**
20+
* @var \Prologue\Alerts\AlertsMessageBag
21+
*/
22+
private $alert;
23+
24+
/**
25+
* @var \Pterodactyl\Services\Api\KeyCreationService
26+
*/
27+
private $keyCreationService;
28+
29+
/**
30+
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
31+
*/
32+
private $repository;
33+
34+
/**
35+
* ApplicationApiController constructor.
36+
*
37+
* @param \Prologue\Alerts\AlertsMessageBag $alert
38+
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
39+
* @param \Pterodactyl\Services\Api\KeyCreationService $keyCreationService
40+
*/
41+
public function __construct(
42+
AlertsMessageBag $alert,
43+
ApiKeyRepositoryInterface $repository,
44+
KeyCreationService $keyCreationService
45+
) {
46+
$this->alert = $alert;
47+
$this->keyCreationService = $keyCreationService;
48+
$this->repository = $repository;
49+
}
50+
51+
/**
52+
* Render view showing all of a user's application API keys.
53+
*
54+
* @param \Illuminate\Http\Request $request
55+
* @return \Illuminate\View\View
56+
*/
57+
public function index(Request $request): View
58+
{
59+
return view('admin.api.index', [
60+
'keys' => $this->repository->getApplicationKeys($request->user()),
61+
]);
62+
}
63+
64+
/**
65+
* Render view allowing an admin to create a new application API key.
66+
*
67+
* @return \Illuminate\View\View
68+
*/
69+
public function create(): View
70+
{
71+
$resources = AdminAcl::getResourceList();
72+
sort($resources);
73+
74+
return view('admin.api.new', [
75+
'resources' => $resources,
76+
'permissions' => [
77+
'r' => AdminAcl::READ,
78+
'rw' => AdminAcl::READ | AdminAcl::WRITE,
79+
'n' => AdminAcl::NONE,
80+
],
81+
]);
82+
}
83+
84+
/**
85+
* Store the new key and redirect the user back to the application key listing.
86+
*
87+
* @param \Pterodactyl\Http\Requests\Admin\Api\StoreApplicationApiKeyRequest $request
88+
* @return \Illuminate\Http\RedirectResponse
89+
*
90+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
91+
*/
92+
public function store(StoreApplicationApiKeyRequest $request): RedirectResponse
93+
{
94+
$this->keyCreationService->setKeyType(ApiKey::TYPE_APPLICATION)->handle([
95+
'memo' => $request->input('memo'),
96+
'user_id' => $request->user()->id,
97+
], $request->getKeyPermissions());
98+
99+
$this->alert->success('A new application API key has been generated for your account.')->flash();
100+
101+
return redirect()->route('admin.api.index');
102+
}
103+
104+
/**
105+
* Delete an application API key from the database.
106+
*
107+
* @param \Illuminate\Http\Request $request
108+
* @param string $identifier
109+
* @return \Illuminate\Http\Response
110+
*/
111+
public function delete(Request $request, string $identifier): Response
112+
{
113+
$this->repository->deleteApplicationKey($request->user(), $identifier);
114+
115+
return response('', 204);
116+
}
117+
}

app/Http/Kernel.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
2222
use Pterodactyl\Http\Middleware\Api\Admin\AuthenticateKey;
2323
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
24+
use Pterodactyl\Http\Middleware\Api\Admin\AuthenticateUser;
2425
use Pterodactyl\Http\Middleware\Api\Admin\SetSessionDriver;
2526
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
2627
use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser;
@@ -66,10 +67,11 @@ class Kernel extends HttpKernel
6667
RequireTwoFactorAuthentication::class,
6768
],
6869
'api' => [
69-
'throttle:60,1',
70+
'throttle:120,1',
7071
SubstituteBindings::class,
7172
SetSessionDriver::class,
7273
AuthenticateKey::class,
74+
AuthenticateUser::class,
7375
AuthenticateIPAccess::class,
7476
],
7577
'daemon' => [
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Middleware\Api\Admin;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
8+
9+
class AuthenticateUser
10+
{
11+
/**
12+
* Authenticate that the currently authenticated user is an administrator
13+
* and should be allowed to proceede through the application API.
14+
*
15+
* @param \Illuminate\Http\Request $request
16+
* @param \Closure $next
17+
* @return mixed
18+
*/
19+
public function handle(Request $request, Closure $next)
20+
{
21+
if (is_null($request->user()) || ! $request->user()->root_admin) {
22+
throw new AccessDeniedHttpException;
23+
}
24+
25+
return $next($request);
26+
}
27+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Admin\Api;
4+
5+
use Pterodactyl\Models\ApiKey;
6+
use Pterodactyl\Services\Acl\Api\AdminAcl;
7+
use Pterodactyl\Http\Requests\Admin\AdminFormRequest;
8+
9+
class StoreApplicationApiKeyRequest extends AdminFormRequest
10+
{
11+
/**
12+
* @return array
13+
*/
14+
public function rules()
15+
{
16+
$modelRules = ApiKey::getCreateRules();
17+
18+
return collect(AdminAcl::getResourceList())->mapWithKeys(function ($resource) use ($modelRules) {
19+
return [AdminAcl::COLUMN_IDENTIFER . $resource => $modelRules['r_' . $resource]];
20+
})->merge(['memo' => $modelRules['memo']])->toArray();
21+
}
22+
23+
/**
24+
* @return array
25+
*/
26+
public function attributes()
27+
{
28+
return [
29+
'memo' => 'Description',
30+
];
31+
}
32+
33+
public function getKeyPermissions(): array
34+
{
35+
return collect($this->validated())->filter(function ($value, $key) {
36+
return substr($key, 0, strlen(AdminAcl::COLUMN_IDENTIFER)) === AdminAcl::COLUMN_IDENTIFER;
37+
})->toArray();
38+
}
39+
}

app/Repositories/Eloquent/ApiKeyRepository.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ public function getAccountKeys(User $user): Collection
3232
->get($this->getColumns());
3333
}
3434

35+
/**
36+
* Get all of the application API keys that exist for a specific user.
37+
*
38+
* @param \Pterodactyl\Models\User $user
39+
* @return \Illuminate\Support\Collection
40+
*/
41+
public function getApplicationKeys(User $user): Collection
42+
{
43+
return $this->getBuilder()->where('user_id', $user->id)
44+
->where('key_type', ApiKey::TYPE_APPLICATION)
45+
->get($this->getColumns());
46+
}
47+
3548
/**
3649
* Delete an account API key from the panel for a specific user.
3750
*
@@ -46,4 +59,19 @@ public function deleteAccountKey(User $user, string $identifier): int
4659
->where('identifier', $identifier)
4760
->delete();
4861
}
62+
63+
/**
64+
* Delete an application API key from the panel for a specific user.
65+
*
66+
* @param \Pterodactyl\Models\User $user
67+
* @param string $identifier
68+
* @return int
69+
*/
70+
public function deleteApplicationKey(User $user, string $identifier): int
71+
{
72+
return $this->getBuilder()->where('user_id', $user->id)
73+
->where('key_type', ApiKey::TYPE_APPLICATION)
74+
->where('identifier', $identifier)
75+
->delete();
76+
}
4977
}

app/Services/Acl/Api/AdminAcl.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Pterodactyl\Services\Acl\Api;
44

5+
use ReflectionClass;
56
use Pterodactyl\Models\ApiKey;
67

78
class AdminAcl
@@ -22,7 +23,7 @@ class AdminAcl
2223

2324
/**
2425
* Resources that are available on the API and can contain a permissions
25-
* set for each key. These are stored in the database as permission_{resource}.
26+
* set for each key. These are stored in the database as r_{resource}.
2627
*/
2728
const RESOURCE_SERVERS = 'servers';
2829
const RESOURCE_NODES = 'nodes';
@@ -63,4 +64,18 @@ public static function check(ApiKey $key, string $resource, int $action = self::
6364
{
6465
return self::can(data_get($key, self::COLUMN_IDENTIFER . $resource, self::NONE), $action);
6566
}
67+
68+
/**
69+
* Return a list of all resource constants defined in this ACL.
70+
*
71+
* @return array
72+
*/
73+
public static function getResourceList(): array
74+
{
75+
$reflect = new ReflectionClass(__CLASS__);
76+
77+
return collect($reflect->getConstants())->filter(function ($value, $key) {
78+
return substr($key, 0, 9) === 'RESOURCE_';
79+
})->values()->toArray();
80+
}
6681
}

database/migrations/2018_01_13_145209_AddLastUsedAtColumn.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ public function up()
1717
$table->unsignedTinyInteger('key_type')->after('user_id')->default(0);
1818
$table->timestamp('last_used_at')->after('memo')->nullable();
1919
$table->dropColumn('expires_at');
20+
21+
$table->dropForeign(['user_id']);
22+
});
23+
24+
Schema::table('api_keys', function (Blueprint $table) {
25+
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
2026
});
2127
}
2228

@@ -30,6 +36,11 @@ public function down()
3036
Schema::table('api_keys', function (Blueprint $table) {
3137
$table->timestamp('expires_at')->after('memo')->nullable();
3238
$table->dropColumn('last_used_at', 'key_type');
39+
$table->dropForeign(['user_id']);
40+
});
41+
42+
Schema::table('api_keys', function (Blueprint $table) {
43+
$table->foreign('user_id')->references('id')->on('users');
3344
});
3445
}
3546
}

public/js/laroute.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)