Skip to content

Commit f9fc3f4

Browse files
committed
Update interface to begin change to seperate account API keys and application keys
Main difference is permissions, cleaner UI for normal users, and account keys use permissions assigned to servers and subusers while application keys use R/W ACLs stored in the key table.
1 parent 28ebd18 commit f9fc3f4

File tree

18 files changed

+311
-297
lines changed

18 files changed

+311
-297
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace Pterodactyl\Console\Commands\Migration;
4+
5+
use Pterodactyl\Models\ApiKey;
6+
use Illuminate\Console\Command;
7+
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
8+
9+
class CleanOrphanedApiKeysCommand extends Command
10+
{
11+
/**
12+
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
13+
*/
14+
private $repository;
15+
16+
/**
17+
* @var string
18+
*/
19+
protected $signature = 'p:migration:clean-orphaned-keys';
20+
21+
/**
22+
* @var string
23+
*/
24+
protected $description = 'Cleans API keys from the database that are not assigned a specific role.';
25+
26+
/**
27+
* CleanOrphanedApiKeysCommand constructor.
28+
*
29+
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
30+
*/
31+
public function __construct(ApiKeyRepositoryInterface $repository)
32+
{
33+
parent::__construct();
34+
35+
$this->repository = $repository;
36+
}
37+
38+
/**
39+
* Delete all orphaned API keys from the database when upgrading from 0.6 to 0.7.
40+
*
41+
* @return null|void
42+
*/
43+
public function handle()
44+
{
45+
$count = $this->repository->findCountWhere([['key_type', '=', ApiKey::TYPE_NONE]]);
46+
$continue = $this->confirm(
47+
'This action will remove ' . $count . ' keys from the database. Are you sure you wish to continue?', false
48+
);
49+
50+
if (! $continue) {
51+
return null;
52+
}
53+
54+
$this->info('Deleting keys...');
55+
$this->repository->deleteWhere([['key_type', '=', ApiKey::TYPE_NONE]]);
56+
$this->info('Keys were successfully deleted.');
57+
}
58+
}
Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
<?php
2-
/**
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5-
*
6-
* This software is licensed under the terms of the MIT license.
7-
* https://opensource.org/licenses/MIT
8-
*/
92

103
namespace Pterodactyl\Contracts\Repository;
114

12-
use Pterodactyl\Models\ApiKey;
5+
use Pterodactyl\Models\User;
6+
use Illuminate\Support\Collection;
137

148
interface ApiKeyRepositoryInterface extends RepositoryInterface
159
{
1610
/**
17-
* Load permissions for a key onto the model.
11+
* Get all of the account API keys that exist for a specific user.
1812
*
19-
* @param \Pterodactyl\Models\ApiKey $model
20-
* @param bool $refresh
21-
* @deprecated
22-
* @return \Pterodactyl\Models\ApiKey
13+
* @param \Pterodactyl\Models\User $user
14+
* @return \Illuminate\Support\Collection
2315
*/
24-
public function loadPermissions(ApiKey $model, bool $refresh = false): ApiKey;
16+
public function getAccountKeys(User $user): Collection;
17+
18+
/**
19+
* Delete an account API key from the panel for a specific user.
20+
*
21+
* @param \Pterodactyl\Models\User $user
22+
* @param string $identifier
23+
* @return int
24+
*/
25+
public function deleteAccountKey(User $user, string $identifier): int;
2526
}

app/Http/Controllers/Base/APIController.php renamed to app/Http/Controllers/Base/AccountKeyController.php

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
namespace Pterodactyl\Http\Controllers\Base;
44

5+
use Illuminate\View\View;
56
use Illuminate\Http\Request;
7+
use Illuminate\Http\Response;
8+
use Pterodactyl\Models\ApiKey;
69
use Prologue\Alerts\AlertsMessageBag;
710
use Pterodactyl\Http\Controllers\Controller;
811
use Pterodactyl\Services\Api\KeyCreationService;
9-
use Pterodactyl\Http\Requests\Base\ApiKeyFormRequest;
12+
use Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest;
1013
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
1114

12-
class APIController extends Controller
15+
class AccountKeyController extends Controller
1316
{
1417
/**
1518
* @var \Prologue\Alerts\AlertsMessageBag
@@ -44,68 +47,60 @@ public function __construct(
4447
}
4548

4649
/**
47-
* Display base API index page.
50+
* Display a listing of all account API keys.
4851
*
4952
* @param \Illuminate\Http\Request $request
5053
* @return \Illuminate\View\View
5154
*/
52-
public function index(Request $request)
55+
public function index(Request $request): View
5356
{
5457
return view('base.api.index', [
55-
'keys' => $this->repository->findWhere([['user_id', '=', $request->user()->id]]),
58+
'keys' => $this->repository->getAccountKeys($request->user()),
5659
]);
5760
}
5861

5962
/**
60-
* Display API key creation page.
63+
* Display account API key creation page.
6164
*
6265
* @param \Illuminate\Http\Request $request
6366
* @return \Illuminate\View\View
6467
*/
65-
public function create(Request $request)
68+
public function create(Request $request): View
6669
{
70+
return view('base.api.new');
6771
}
6872

6973
/**
70-
* Handle saving new API key.
74+
* Handle saving new account API key.
7175
*
72-
* @param \Pterodactyl\Http\Requests\Base\ApiKeyFormRequest $request
76+
* @param \Pterodactyl\Http\Requests\Base\StoreAccountKeyRequest $request
7377
* @return \Illuminate\Http\RedirectResponse
7478
*
75-
* @throws \Exception
7679
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
7780
*/
78-
public function store(ApiKeyFormRequest $request)
81+
public function store(StoreAccountKeyRequest $request)
7982
{
80-
$adminPermissions = [];
81-
if ($request->user()->root_admin) {
82-
$adminPermissions = $request->input('admin_permissions', []);
83-
}
84-
85-
$secret = $this->keyService->handle([
83+
$this->keyService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([
8684
'user_id' => $request->user()->id,
8785
'allowed_ips' => $request->input('allowed_ips'),
8886
'memo' => $request->input('memo'),
89-
], $request->input('permissions', []), $adminPermissions);
87+
]);
9088

9189
$this->alert->success(trans('base.api.index.keypair_created'))->flash();
9290

9391
return redirect()->route('account.api');
9492
}
9593

9694
/**
95+
* Delete an account API key from the Panel via an AJAX request.
96+
*
9797
* @param \Illuminate\Http\Request $request
98-
* @param string $key
98+
* @param string $identifier
9999
* @return \Illuminate\Http\Response
100-
*
101-
* @throws \Exception
102100
*/
103-
public function revoke(Request $request, $key)
101+
public function revoke(Request $request, string $identifier): Response
104102
{
105-
$this->repository->deleteWhere([
106-
['user_id', '=', $request->user()->id],
107-
['token', '=', $key],
108-
]);
103+
$this->repository->deleteAccountKey($request->user(), $identifier);
109104

110105
return response('', 204);
111106
}

app/Http/Middleware/Api/Admin/AuthenticateKey.php

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

55
use Closure;
6+
use Cake\Chronos\Chronos;
67
use Illuminate\Http\Request;
78
use Pterodactyl\Models\ApiKey;
89
use Illuminate\Auth\AuthManager;
@@ -51,8 +52,8 @@ public function __construct(ApiKeyRepositoryInterface $repository, AuthManager $
5152
* @param \Closure $next
5253
* @return mixed
5354
*
54-
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
55-
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
55+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
56+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
5657
*/
5758
public function handle(Request $request, Closure $next)
5859
{
@@ -65,7 +66,10 @@ public function handle(Request $request, Closure $next)
6566
$token = substr($raw, ApiKey::IDENTIFIER_LENGTH);
6667

6768
try {
68-
$model = $this->repository->findFirstWhere([['identifier', '=', $identifier]]);
69+
$model = $this->repository->findFirstWhere([
70+
['identifier', '=', $identifier],
71+
['key_type', '=', ApiKey::TYPE_APPLICATION],
72+
]);
6973
} catch (RecordNotFoundException $exception) {
7074
throw new AccessDeniedHttpException;
7175
}
@@ -76,6 +80,7 @@ public function handle(Request $request, Closure $next)
7680

7781
$this->auth->guard()->loginUsingId($model->user_id);
7882
$request->attributes->set('api_key', $model);
83+
$this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => Chronos::now()]);
7984

8085
return $next($request);
8186
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Base;
4+
5+
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
6+
7+
class StoreAccountKeyRequest extends FrontendUserFormRequest
8+
{
9+
/**
10+
* Rules to validate the request input aganist before storing
11+
* an account API key.
12+
*
13+
* @return array
14+
*/
15+
public function rules()
16+
{
17+
return [
18+
'memo' => 'required|nullable|string|max:500',
19+
'allowed_ips' => 'present',
20+
'allowed_ips.*' => 'sometimes|string',
21+
];
22+
}
23+
}

app/Models/ApiKey.php

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use Sofa\Eloquence\Validable;
77
use Illuminate\Database\Eloquent\Model;
88
use Pterodactyl\Services\Acl\Api\AdminAcl;
9-
use Illuminate\Contracts\Encryption\Encrypter;
109
use Sofa\Eloquence\Contracts\CleansAttributes;
1110
use Sofa\Eloquence\Contracts\Validable as ValidableContract;
1211

@@ -18,7 +17,7 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract
1817
* Different API keys that can exist on the system.
1918
*/
2019
const TYPE_NONE = 0;
21-
const TYPE_USER = 1;
20+
const TYPE_ACCOUNT = 1;
2221
const TYPE_APPLICATION = 2;
2322
const TYPE_DAEMON_USER = 3;
2423
const TYPE_DAEMON_APPLICATION = 4;
@@ -70,6 +69,7 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract
7069
'token',
7170
'allowed_ips',
7271
'memo',
72+
'last_used_at',
7373
];
7474

7575
/**
@@ -90,6 +90,7 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract
9090
'memo' => 'required',
9191
'user_id' => 'required',
9292
'token' => 'required',
93+
'key_type' => 'present',
9394
];
9495

9596
/**
@@ -99,6 +100,7 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract
99100
*/
100101
protected static $dataIntegrityRules = [
101102
'user_id' => 'exists:users,id',
103+
'key_type' => 'integer|min:0|max:4',
102104
'identifier' => 'string|size:16|unique:api_keys,identifier',
103105
'token' => 'string',
104106
'memo' => 'nullable|string|max:500',
@@ -123,14 +125,4 @@ class ApiKey extends Model implements CleansAttributes, ValidableContract
123125
self::UPDATED_AT,
124126
'last_used_at',
125127
];
126-
127-
/**
128-
* Return a decrypted version of the token.
129-
*
130-
* @return string
131-
*/
132-
public function getDecryptedTokenAttribute()
133-
{
134-
return app()->make(Encrypter::class)->decrypt($this->token);
135-
}
136128
}

app/Repositories/Eloquent/ApiKeyRepository.php

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace Pterodactyl\Repositories\Eloquent;
44

5+
use Pterodactyl\Models\User;
56
use Pterodactyl\Models\ApiKey;
7+
use Illuminate\Support\Collection;
68
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
79

810
class ApiKeyRepository extends EloquentRepository implements ApiKeyRepositoryInterface
@@ -18,19 +20,30 @@ public function model()
1820
}
1921

2022
/**
21-
* Load permissions for a key onto the model.
23+
* Get all of the account API keys that exist for a specific user.
2224
*
23-
* @param \Pterodactyl\Models\ApiKey $model
24-
* @param bool $refresh
25-
* @deprecated
26-
* @return \Pterodactyl\Models\ApiKey
25+
* @param \Pterodactyl\Models\User $user
26+
* @return \Illuminate\Support\Collection
2727
*/
28-
public function loadPermissions(ApiKey $model, bool $refresh = false): ApiKey
28+
public function getAccountKeys(User $user): Collection
2929
{
30-
if (! $model->relationLoaded('permissions') || $refresh) {
31-
$model->load('permissions');
32-
}
30+
return $this->getBuilder()->where('user_id', $user->id)
31+
->where('key_type', ApiKey::TYPE_ACCOUNT)
32+
->get($this->getColumns());
33+
}
3334

34-
return $model;
35+
/**
36+
* Delete an account 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 deleteAccountKey(User $user, string $identifier): int
43+
{
44+
return $this->getBuilder()->where('user_id', $user->id)
45+
->where('key_type', ApiKey::TYPE_ACCOUNT)
46+
->where('identifier', $identifier)
47+
->delete();
3548
}
3649
}

0 commit comments

Comments
 (0)