From 53ec2c55ec9b667d4f5e852e46a24c4b495dba74 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 20 Oct 2016 18:20:58 -0400 Subject: [PATCH] Add front-end support for adding and deleting API keys. --- app/Http/Controllers/Base/APIController.php | 30 +- app/Http/Routes/APIRoutes.php | 2 +- app/Http/Routes/BaseRoutes.php | 4 + app/Repositories/APIRepository.php | 117 +++++-- public/themes/default/css/pterodactyl.css | 4 + resources/views/base/api/index.blade.php | 43 +++ resources/views/base/api/new.blade.php | 368 +++++++++++--------- resources/views/layouts/master.blade.php | 36 +- 8 files changed, 379 insertions(+), 225 deletions(-) diff --git a/app/Http/Controllers/Base/APIController.php b/app/Http/Controllers/Base/APIController.php index 42109857a..da9a20d78 100644 --- a/app/Http/Controllers/Base/APIController.php +++ b/app/Http/Controllers/Base/APIController.php @@ -25,9 +25,12 @@ namespace Pterodactyl\Http\Controllers\Base; use Alert; +use Log; use Pterodactyl\Models; +use Pterodactyl\Repositories\APIRepository; +use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; @@ -45,7 +48,6 @@ class APIController extends Controller return view('base.api.index', [ 'keys' => $keys ]); - } public function new(Request $request) @@ -55,6 +57,32 @@ class APIController extends Controller public function save(Request $request) { + try { + $repo = new APIRepository($request->user()); + $secret = $repo->new($request->except(['_token'])); + Alert::success('An API Keypair has successfully been generated. The API secret for this public key is shown below and will not be shown again.

' . $secret . '')->flash(); + return redirect()->route('account.api'); + } catch (DisplayValidationException $ex) { + return redirect()->route('account.api.new')->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occured while attempting to add this API key.')->flash(); + } + return redirect()->route('account.api.new')->withInput(); + } + public function revoke(Request $request, $key) + { + try { + $repo = new APIRepository($request->user()); + $repo->revoke($key); + return response('', 204); + } catch (\Exception $ex) { + return response()->json([ + 'error' => 'An error occured while attempting to remove this key.' + ], 503); + } } } diff --git a/app/Http/Routes/APIRoutes.php b/app/Http/Routes/APIRoutes.php index 5ba792b35..33d3d7177 100755 --- a/app/Http/Routes/APIRoutes.php +++ b/app/Http/Routes/APIRoutes.php @@ -34,7 +34,7 @@ class APIRoutes $api = app('Dingo\Api\Routing\Router'); $api->version('v1', ['prefix' => 'api/me', 'middleware' => 'api.auth'], function ($api) { $api->get('/', [ - 'as' => 'api.user', + 'as' => 'api.user.me', 'uses' => 'Pterodactyl\Http\Controllers\API\User\InfoController@me' ]); diff --git a/app/Http/Routes/BaseRoutes.php b/app/Http/Routes/BaseRoutes.php index 60ac32a8a..6841ea1b6 100644 --- a/app/Http/Routes/BaseRoutes.php +++ b/app/Http/Routes/BaseRoutes.php @@ -88,6 +88,10 @@ class BaseRoutes { $router->post('/new', [ 'uses' => 'Base\APIController@save' ]); + + $router->delete('/revoke/{key}', [ + 'uses' => 'Base\APIController@revoke' + ]); }); // TOTP Routes diff --git a/app/Repositories/APIRepository.php b/app/Repositories/APIRepository.php index b92a7cab0..b18c8ec90 100644 --- a/app/Repositories/APIRepository.php +++ b/app/Repositories/APIRepository.php @@ -23,6 +23,7 @@ */ namespace Pterodactyl\Repositories; +use Auth; use DB; use Crypt; use Validator; @@ -40,38 +41,51 @@ class APIRepository * @var array */ protected $permissions = [ - '*', + 'admin' => [ + '*', - // User Management Routes - 'api.users.list', - 'api.users.create', - 'api.users.view', - 'api.users.update', - 'api.users.delete', + // User Management Routes + 'users.list', + 'users.create', + 'users.view', + 'users.update', + 'users.delete', - // Server Manaement Routes - 'api.servers.list', - 'api.servers.create', - 'api.servers.view', - 'api.servers.config', - 'api.servers.build', - 'api.servers.suspend', - 'api.servers.unsuspend', - 'api.servers.delete', + // Server Manaement Routes + 'servers.list', + 'servers.create', + 'servers.view', + 'servers.config', + 'servers.build', + 'servers.suspend', + 'servers.unsuspend', + 'servers.delete', - // Node Management Routes - 'api.nodes.list', - 'api.nodes.create', - 'api.nodes.list', - 'api.nodes.allocations', - 'api.nodes.delete', + // Node Management Routes + 'nodes.list', + 'nodes.create', + 'nodes.list', + 'nodes.allocations', + 'nodes.delete', - // Service Routes - 'api.services.list', - 'api.services.view', + // Service Routes + 'services.list', + 'services.view', - // Location Routes - 'api.locations.list', + // Location Routes + 'locations.list', + + ], + 'user' => [ + '*', + + // Informational + 'me', + + // Server Control + 'server', + 'server.power', + ], ]; /** @@ -80,12 +94,17 @@ class APIRepository */ protected $allowed = []; + protected $user; + /** * Constructor */ - public function __construct() + public function __construct(Models\User $user = null) { - // + $this->user = is_null($user) ? Auth::user() : $user; + if (is_null($this->user)) { + throw new \Exception('Cannot access API Repository without passing a user to __construct().'); + } } /** @@ -101,7 +120,9 @@ class APIRepository public function new(array $data) { $validator = Validator::make($data, [ - 'permissions' => 'required|array' + 'memo' => 'string|max:500', + 'permissions' => 'sometimes|required|array', + 'adminPermissions' => 'sometimes|required|array' ]); $validator->after(function($validator) use ($data) { @@ -125,31 +146,53 @@ class APIRepository } DB::beginTransaction(); - try { - $secretKey = str_random(16) . '.' . str_random(15); + $secretKey = str_random(16) . '.' . str_random(7) . '.' . str_random(7); $key = new Models\APIKey; $key->fill([ + 'user' => $this->user->id, 'public' => str_random(16), 'secret' => Crypt::encrypt($secretKey), - 'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed) + 'allowed_ips' => empty($this->allowed) ? null : json_encode($this->allowed), + 'memo' => $data['memo'], + 'expires_at' => null ]); $key->save(); - foreach($data['permissions'] as $permission) { - if (in_array($permission, $this->permissions)) { + foreach($data['permissions'] as $permNode) { + if (!strpos($permNode, ':')) continue; + + list($toss, $permission) = explode(':', $permNode); + if (in_array('api.user.' . $permission, $this->permissions['user'])) { $model = new Models\APIPermission; $model->fill([ 'key_id' => $key->id, - 'permission' => $permission + 'permission' => 'api.user.' . $permission ]); $model->save(); } } + if ($this->user->root_admin === 1) { + foreach($data['permissions'] as $permNode) { + if (!strpos($permNode, ':')) continue; + + list($toss, $permission) = explode(':', $permNode); + if (in_array('api.admin.' . $permission, $this->permissions['admin'])) { + $model = new Models\APIPermission; + $model->fill([ + 'key_id' => $key->id, + 'permission' => 'api.admin.' . $permission + ]); + $model->save(); + } + } + } + DB::commit(); return $secretKey; } catch (\Exception $ex) { + DB::rollBack(); throw $ex; } @@ -169,7 +212,7 @@ class APIRepository DB::beginTransaction(); try { - $model = Models\APIKey::where('public', $key)->firstOrFail(); + $model = Models\APIKey::where('public', $key)->where('user', $this->user->id)->firstOrFail(); $permissions = Models\APIPermission::where('key_id', $model->id)->delete(); $model->delete(); diff --git a/public/themes/default/css/pterodactyl.css b/public/themes/default/css/pterodactyl.css index 86cd27ca3..c25130929 100755 --- a/public/themes/default/css/pterodactyl.css +++ b/public/themes/default/css/pterodactyl.css @@ -267,3 +267,7 @@ li.btn.btn-default.pill:active,li.btn.btn-default.pill:focus,li.btn.btn-default. .fuelux .wizard .step-content > .alert { margin-bottom: 0 !important; } + +.fuelux .wizard .steps-container { + background-color: #eee; +} diff --git a/resources/views/base/api/index.blade.php b/resources/views/base/api/index.blade.php index 75891d158..0fcc69a79 100644 --- a/resources/views/base/api/index.blade.php +++ b/resources/views/base/api/index.blade.php @@ -24,6 +24,12 @@ @section('sidebar-server') @endsection +@section('scripts') + @parent + {!! Theme::css('css/vendor/sweetalert/sweetalert.min.css') !!} + {!! Theme::js('js/vendor/sweetalert/sweetalert.min.js') !!} +@endsection + @section('content')
@@ -61,6 +67,43 @@ @endsection diff --git a/resources/views/base/api/new.blade.php b/resources/views/base/api/new.blade.php index e1049a2bb..e8ff08a4f 100644 --- a/resources/views/base/api/new.blade.php +++ b/resources/views/base/api/new.blade.php @@ -41,10 +41,6 @@ 3Security -
  • - 4Finish - -
  • @@ -54,185 +50,225 @@
    +
    - {{-- --}} -
    -
    -
    -
    -
    -
    +
    +
    Any servers that you are a subuser for will be accessible through this API with the same permissions that you currently have.
    +
    +
    +

    Base Information


    +
    +
    +
    +
    -
    -
    -

    User Management


    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +
    +

    Server Management


    +
    +
    -
    -

    Server Management


    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Node Management


    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Service Management


    -
    -
    -
    -
    -

    Location Management


    -
    -
    +
    +
    -
    -
    - -
    - -

    Enter a line delimitated list of IPs that are allowed to access the API using this key. CIDR notation is allowed. Leave blank to allow any IP.

    +
    +
    +
    +
    +
    +
    -
    - Whoa +
    +
    +

    User Management


    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Server Management


    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    - +
    +
    +

    Node Management


    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Service Management


    +
    +
    +
    +
    +

    Location Management


    +
    +
    +
    +
    +
    +
    +
    + +
    + +

    Enter a breif description of what this API key will be used for.

    +
    +
    +
    + +
    + +

    Enter a line delimitated list of IPs that are allowed to access the API using this key. CIDR notation is allowed. Leave blank to allow any IP.

    +
    +
    +
    + {!! csrf_field() !!} +
    - - {{-- --}}
    @endsection diff --git a/resources/views/layouts/master.blade.php b/resources/views/layouts/master.blade.php index 522155139..903ec0a4e 100644 --- a/resources/views/layouts/master.blade.php +++ b/resources/views/layouts/master.blade.php @@ -260,29 +260,25 @@
    - @section('resp-errors') - @if (count($errors) > 0) -
    + @if (count($errors) > 0) +
    + + {{ trans('strings.whoops') }}! {{ trans('auth.errorencountered') }}

    +
      + @foreach ($errors->all() as $error) +
    • {{ $error }}
    • + @endforeach +
    +
    + @endif + @foreach (Alert::getMessages() as $type => $messages) + @foreach ($messages as $message) + - @endif - @show - @section('resp-alerts') - @foreach (Alert::getMessages() as $type => $messages) - @foreach ($messages as $message) - - @endforeach @endforeach - @show + @endforeach