Skip to content

Commit 3e595ca

Browse files
committed
Add API Management to admin CP
1 parent ade16e6 commit 3e595ca

File tree

9 files changed

+395
-4
lines changed

9 files changed

+395
-4
lines changed

app/Http/Controllers/Admin/APIController.php

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44

55
use Alert;
66
use Log;
7-
use Pterodactyl\Models;
87

8+
use Pterodactyl\Models;
9+
use Pterodactyl\Repositories\APIRepository;
910
use Pterodactyl\Http\Controllers\Controller;
11+
12+
use Pterodactyl\Exceptions\DisplayValidationException;
13+
use Pterodactyl\Exceptions\DisplayException;
14+
1015
use Illuminate\Http\Request;
1116

1217
class APIController extends Controller
@@ -29,4 +34,40 @@ public function getIndex(Request $request)
2934
]);
3035
}
3136

37+
public function getNew(Request $request)
38+
{
39+
return view('admin.api.new');
40+
}
41+
42+
public function postNew(Request $request)
43+
{
44+
try {
45+
$api = new APIRepository;
46+
$secret = $api->new($request->except(['_token']));
47+
Alert::info('An API Keypair has successfully been generated. The API secret for this public key is shown below and will not be shown again.<br /><br />Secret: <code>' . $secret . '</code>')->flash();
48+
return redirect()->route('admin.api');
49+
} catch (DisplayValidationException $ex) {
50+
return redirect()->route('admin.api.new')->withErrors(json_decode($ex->getMessage()))->withInput();
51+
} catch (DisplayException $ex) {
52+
Alert::danger($ex->getMessage())->flash();
53+
} catch (\Exception $ex) {
54+
Log::error($ex);
55+
Alert::danger('An unhandled exception occured while attempting to add this API key.')->flash();
56+
}
57+
return redirect()->route('admin.api.new')->withInput();
58+
}
59+
60+
public function deleteRevokeKey(Request $request, $key)
61+
{
62+
try {
63+
$api = new APIRepository;
64+
$api->revoke($key);
65+
return response('', 204);
66+
} catch (\Exception $ex) {
67+
return response()->json([
68+
'error' => 'An error occured while attempting to remove this key.'
69+
], 503);
70+
}
71+
}
72+
3273
}

app/Http/Routes/AdminRoutes.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,12 @@ public function map(Router $router) {
235235
'as' => 'admin.api.new',
236236
'uses' => 'Admin\APIController@getNew'
237237
]);
238+
$router->post('/new', [
239+
'uses' => 'Admin\APIController@postNew'
240+
]);
238241
$router->delete('/revoke/{key?}', [
239242
'as' => 'admin.api.revoke',
240-
'uses' => 'Admin\APIController@deleteKey'
243+
'uses' => 'Admin\APIController@deleteRevokeKey'
241244
]);
242245
});
243246

app/Models/APIPermission.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,11 @@ class APIPermission extends Model
2121
*/
2222
protected $guarded = ['id'];
2323

24+
/**
25+
* Disable timestamps for this table.
26+
*
27+
* @var boolean
28+
*/
29+
public $timestamps = false;
2430

2531
}

app/Repositories/APIRepository.php

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
namespace Pterodactyl\Repositories;
4+
5+
use DB;
6+
use Validator;
7+
use IPTools\Network;
8+
9+
use Pterodactyl\Models;
10+
use Pterodactyl\Exceptions\DisplayException;
11+
use Pterodactyl\Exceptions\DisplayValidationException;
12+
13+
class APIRepository
14+
{
15+
16+
/**
17+
* Valid API permissions.
18+
* @var array
19+
*/
20+
protected $permissions = [
21+
'*',
22+
23+
// User Management Routes
24+
'api.users',
25+
'api.users.view',
26+
'api.users.post',
27+
'api.users.delete',
28+
'api.users.patch',
29+
30+
// Server Manaement Routes
31+
'api.servers',
32+
'api.servers.view',
33+
'api.servers.post',
34+
'api.servers.suspend',
35+
'api.servers.unsuspend',
36+
'api.servers.delete',
37+
38+
// Node Management Routes
39+
'api.nodes',
40+
'api.nodes.view',
41+
'api.nodes.post',
42+
'api.nodes.view_allocations',
43+
'api.nodes.delete',
44+
45+
// Assorted Routes
46+
'api.services',
47+
'api.services.view',
48+
'api.locations',
49+
];
50+
51+
/**
52+
* Holder for listing of allowed IPs when creating a new key.
53+
* @var array
54+
*/
55+
protected $allowed = [];
56+
57+
/**
58+
* Constructor
59+
*/
60+
public function __construct()
61+
{
62+
//
63+
}
64+
65+
/**
66+
* Create a New API Keypair on the system.
67+
*
68+
* @param array $data An array with a permissions and allowed_ips key.
69+
*
70+
* @throws Pterodactyl\Exceptions\DisplayException if there was an error that can be safely displayed to end-users.
71+
* @throws Pterodactyl\Exceptions\DisplayValidationException if there was a validation error.
72+
*
73+
* @return string Returns the generated secret token.
74+
*/
75+
public function new(array $data)
76+
{
77+
$validator = Validator::make($data, [
78+
'permissions' => 'required|array'
79+
]);
80+
81+
$validator->after(function($validator) use ($data) {
82+
if (array_key_exists('allowed_ips', $data) && !empty($data['allowed_ips'])) {
83+
foreach(explode("\n", $data['allowed_ips']) as $ip) {
84+
$ip = trim($ip);
85+
try {
86+
Network::parse($ip);
87+
array_push($this->allowed, $ip);
88+
} catch (\Exception $ex) {
89+
$validator->errors()->add('allowed_ips', 'Could not parse IP <' . $ip . '> because it is in an invalid format.');
90+
}
91+
}
92+
}
93+
});
94+
95+
// Run validator, throw catchable and displayable exception if it fails.
96+
// Exception includes a JSON result of failed validation rules.
97+
if ($validator->fails()) {
98+
throw new DisplayValidationException($validator->errors());
99+
}
100+
101+
DB::beginTransaction();
102+
103+
$key = new Models\APIKey;
104+
$key->fill([
105+
'public' => str_random(16),
106+
'secret' => str_random(16) . '.' . str_random(15),
107+
'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed)
108+
]);
109+
$key->save();
110+
111+
foreach($data['permissions'] as $permission) {
112+
if (in_array($permission, $this->permissions)) {
113+
$model = new Models\APIPermission;
114+
$model->fill([
115+
'key_id' => $key->id,
116+
'permission' => $permission
117+
]);
118+
$model->save();
119+
}
120+
}
121+
122+
try {
123+
DB::commit();
124+
return $key->secret;
125+
} catch (\Exception $ex) {
126+
throw $ex;
127+
}
128+
129+
}
130+
131+
/**
132+
* Revokes an API key and associated permissions.
133+
*
134+
* @param string $key The public key.
135+
*
136+
* @throws Illuminate\Database\Eloquent\ModelNotFoundException
137+
*
138+
* @return void
139+
*/
140+
public function revoke(string $key)
141+
{
142+
DB::beginTransaction();
143+
144+
$model = Models\APIKey::where('public', $key)->firstOrFail();
145+
$permissions = Models\APIPermission::where('key_id', $model->id)->delete();
146+
$model->delete();
147+
148+
DB::commit();
149+
}
150+
151+
}

public/css/pterodactyl.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ li.btn.btn-default.pill:active,li.btn.btn-default.pill:focus,li.btn.btn-default.
112112
margin: 0 !important;
113113
}
114114

115+
.fuelux .checkbox.highlight.checked label, .fuelux .checkbox.highlight label {
116+
width: 100%;
117+
}
118+
115119
.btn-allocate-delete {
116120
height:34px;
117121
width:34px;

resources/views/admin/api/index.blade.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@
6161
text: 'Once this API key is revoked any applications currently using it will stop working.',
6262
showCancelButton: true,
6363
allowOutsideClick: true,
64+
closeOnConfirm: false,
6465
confirmButtonText: 'Revoke',
6566
confirmButtonColor: '#d9534f',
67+
showLoaderOnConfirm: true
6668
}, function () {
6769
$.ajax({
6870
method: 'DELETE',
@@ -73,6 +75,8 @@
7375
}).done(function (data) {
7476
swal({
7577
type: 'success',
78+
title: '',
79+
text: 'API Key has been revoked.'
7680
});
7781
self.parent().parent().slideUp();
7882
}).fail(function (jqXHR) {

0 commit comments

Comments
 (0)