From db4df2bfa1705e62286e1744ac694ffcb15e4d3f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 7 Apr 2017 21:25:17 -0400 Subject: [PATCH] Push basis of new API key policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Will need to revisit this another day when I’m fresh to figure out the best method to do this. --- .../API/Admin/ServerController.php | 10 ++- app/Http/Middleware/HMACAuthorization.php | 18 +---- app/Policies/APIKeyPolicy.php | 77 +++++++++++++++++++ app/Policies/ServerPolicy.php | 2 +- app/Providers/AuthServiceProvider.php | 1 + app/Providers/MacroServiceProvider.php | 15 ++++ 6 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 app/Policies/APIKeyPolicy.php diff --git a/app/Http/Controllers/API/Admin/ServerController.php b/app/Http/Controllers/API/Admin/ServerController.php index 716d39dca..793af13ec 100644 --- a/app/Http/Controllers/API/Admin/ServerController.php +++ b/app/Http/Controllers/API/Admin/ServerController.php @@ -60,9 +60,17 @@ class ServerController extends Controller public function view(Request $request, $id) { $server = Server::findOrFail($id); - $fractal = Fractal::create()->item($server); + // dd($request->user()->can('view-node', $request->apiKey())); + + // Have the api key model return a list of includes that would be allowed + // given the permissions they have aleady been granted? + // + // If someone has 'view-node' they would then be able to use ->parseIncludes(['*.node.*']); + // How that logic will work is beyond me currently, but should keep things + // fairly clean? + if ($request->input('include')) { $fractal->parseIncludes(explode(',', $request->input('include'))); } diff --git a/app/Http/Middleware/HMACAuthorization.php b/app/Http/Middleware/HMACAuthorization.php index 6eac236a2..7b4e84b7f 100644 --- a/app/Http/Middleware/HMACAuthorization.php +++ b/app/Http/Middleware/HMACAuthorization.php @@ -92,6 +92,7 @@ class HMACAuthorization $this->checkBearer(); $this->validateRequest(); + $this->validateIPAccess(); $this->validateContents(); Auth::loginUsingId($this->key()->user_id); @@ -137,23 +138,6 @@ class HMACAuthorization if (! $this->key) { throw new AccessDeniedHttpException('Unable to identify requester. Authorization token is invalid.'); } - - if (empty($this->request()->route()->getName())) { - throw new \Exception('Attempting to access un-named route. This should not be possible.'); - } - - $this->validateIPAccess(); - - $query = APIPermission::where('key_id', $this->key()->id) - ->where('permission', $this->request()->route()->getName()); - - if (starts_with($this->request()->route()->getName(), 'api.user')) { - $query->orWhere('permission', 'api.user.*'); - } - - if (! $query->first()) { - throw new AccessDeniedHttpException('You do not have permission to access this resource on the API.'); - } } /** diff --git a/app/Policies/APIKeyPolicy.php b/app/Policies/APIKeyPolicy.php new file mode 100644 index 000000000..caeaae5df --- /dev/null +++ b/app/Policies/APIKeyPolicy.php @@ -0,0 +1,77 @@ +. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +namespace Pterodactyl\Policies; + +use Cache; +use Carbon; +use Pterodactyl\Models\User; +use Pterodactyl\Models\APIKey as Key; +use Pterodactyl\Models\APIPermission as Permission; + +class APIKeyPolicy +{ + /** + * Checks if the API key has permission to perform an action. + * + * @param \Pterodactyl\Models\User $user + * @param \Pterodactyl\Models\APIKey $key + * @param string $permission + * @return bool + */ + private function checkPermission(User $user, Key $key, $permission) + { + $permissions = Cache::remember('APIKeyPolicy.' . $user->uuid . $key->public, Carbon::now()->addSeconds(5), function () use ($key) { + return $key->permissions()->get()->transform(function ($item) { + return $item->permission; + })->values(); + }); + + return $permissions->search($permission, true) !== false; + } + + /** + * Determine if API key is allowed to view all nodes. + * + * @param \Pterodactyl\Models\User $user + * @param \Pterodactyl\Models\APIKey $key + * @return bool + */ + public function listNodes(User $user, Key $key) + { + return $this->checkPermission($user, $key, 'list-nodes'); + } + + /** + * Determine if API key is allowed to view a specific node. + * + * @param \Pterodactyl\Models\User $user + * @param \Pterodactyl\Models\APIKey $key + * @return bool + */ + public function viewNode(User $user, Key $key) + { + return $this->checkPermission($user, $key, 'view-node'); + } +} diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index f375afecc..6b73258fa 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -45,7 +45,7 @@ class ServerPolicy return true; } - $permissions = Cache::remember('ServerPolicy.' . $user->uuid . $server->uuid, Carbon::now()->addSeconds(10), function () use ($user, $server) { + $permissions = Cache::remember('ServerPolicy.' . $user->uuid . $server->uuid, Carbon::now()->addSeconds(5), function () use ($user, $server) { return $user->permissions()->server($server)->get()->transform(function ($item) { return $item->permission; })->values(); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 674d7600a..e1401e844 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -13,6 +13,7 @@ class AuthServiceProvider extends ServiceProvider */ protected $policies = [ 'Pterodactyl\Models\Server' => 'Pterodactyl\Policies\ServerPolicy', + 'Pterodactyl\Models\APIKey' => 'Pterodactyl\Policies\APIKeyPolicy', ]; /** diff --git a/app/Providers/MacroServiceProvider.php b/app/Providers/MacroServiceProvider.php index ad23aaf74..eb5a52c56 100644 --- a/app/Providers/MacroServiceProvider.php +++ b/app/Providers/MacroServiceProvider.php @@ -25,6 +25,7 @@ namespace Pterodactyl\Providers; use File; +use Request; use Illuminate\Support\ServiceProvider; class MacroServiceProvider extends ServiceProvider @@ -48,5 +49,19 @@ class MacroServiceProvider extends ServiceProvider return round($size, ($i < 2) ? 0 : $precision) . ' ' . $units[$i]; }); + + Request::macro('apiKey', function () { + if (! Request::bearerToken()) { + return false; + } + + $parts = explode('.', Request::bearerToken()); + + if (count($parts) === 2) { + return \Pterodactyl\Models\APIKey::where('public', $parts[0])->first(); + } + + return false; + }); } }