Add permissions checking to API middleware list

This commit is contained in:
Dane Everitt 2017-11-19 15:23:37 -06:00
parent 49379bd115
commit bf9708fe4f
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
8 changed files with 206 additions and 210 deletions

View file

@ -24,6 +24,7 @@ use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Pterodactyl\Http\Middleware\API\AuthenticateIPAccess;
use Pterodactyl\Http\Middleware\Daemon\DaemonAuthenticate;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Pterodactyl\Http\Middleware\API\HasPermissionToResource;
use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser;
use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer;
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
@ -95,6 +96,9 @@ class Kernel extends HttpKernel
'bindings' => SubstituteBindings::class,
'recaptcha' => VerifyReCaptcha::class,
// API specific middleware.
'api..user_level' => HasPermissionToResource::class,
// Server specific middleware (used for authenticating access to resources)
//
// These are only used for individual server authentication, and not gloabl

View file

@ -0,0 +1,58 @@
<?php
namespace Pterodactyl\Http\Middleware\API;
use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class HasPermissionToResource
{
/**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
*/
private $repository;
/**
* HasPermissionToResource constructor.
*
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
*/
public function __construct(ApiKeyRepositoryInterface $repository)
{
$this->repository = $repository;
}
/**
* Determine if an API key has permission to access the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $role
* @return mixed
*/
public function handle(Request $request, Closure $next, string $role = 'admin')
{
/** @var \Pterodactyl\Models\APIKey $model */
$model = $request->attributes->get('api_key');
if ($role === 'admin' && ! $request->user()->root_admin) {
throw new NotFoundHttpException;
}
$this->repository->loadPermissions($model);
$routeKey = str_replace(['api.', 'admin.'], '', $request->route()->getName());
$count = $model->getRelation('permissions')->filter(function ($permission) use ($routeKey) {
return $routeKey === str_replace('-', '.', $permission->permission);
})->count();
if ($count === 1) {
return $next($request);
}
throw new AccessDeniedHttpException('Cannot access resource without required `' . $routeKey . '` permission.');
}
}

View file

@ -1,209 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Middleware;
use Auth;
use Crypt;
use Config;
use Closure;
use Debugbar;
use IPTools\IP;
use IPTools\Range;
use Illuminate\Http\Request;
use Pterodactyl\Models\APIKey;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; // 400
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; // 403
class HMACAuthorization
{
/**
* The algorithm to use for handling HMAC encryption.
*
* @var string
*/
const HMAC_ALGORITHM = 'sha256';
/**
* Stored values from the Authorization header.
*
* @var array
*/
protected $token = [];
/**
* The eloquent model for the API key.
*
* @var \Pterodactyl\Models\APIKey
*/
protected $key;
/**
* The illuminate request object.
*
* @var \Illuminate\Http\Request
*/
private $request;
/**
* Construct class instance.
*/
public function __construct()
{
Debugbar::disable();
Config::set('session.driver', 'array');
}
/**
* Handle an incoming request for the API.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$this->request = $request;
$this->checkBearer();
$this->validateRequest();
$this->validateIPAccess();
$this->validateContents();
Auth::loginUsingId($this->key()->user_id);
return $next($request);
}
/**
* Checks that the Bearer token is provided and in a valid format.
*
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
protected function checkBearer()
{
if (empty($this->request()->bearerToken())) {
throw new BadRequestHttpException('Request was missing required Authorization header.');
}
$token = explode('.', $this->request()->bearerToken());
if (count($token) !== 2) {
throw new BadRequestHttpException('The Authorization header passed was not in a validate public/private key format.');
}
$this->token = [
'public' => $token[0],
'hash' => $token[1],
];
}
/**
* Determine if the request contains a valid public API key
* as well as permissions for the resource.
*
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
protected function validateRequest()
{
$this->key = APIKey::where('public', $this->public())->first();
if (! $this->key) {
throw new AccessDeniedHttpException('Unable to identify requester. Authorization token is invalid.');
}
}
/**
* Determine if the requesting IP address is allowed to use this API key.
*
* @return bool
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
protected function validateIPAccess()
{
if (! is_null($this->key()->allowed_ips)) {
foreach (json_decode($this->key()->allowed_ips) as $ip) {
if (Range::parse($ip)->contains(new IP($this->request()->ip()))) {
return true;
}
}
throw new AccessDeniedHttpException('This IP address does not have permission to access the API using these credentials.');
}
return true;
}
/**
* Determine if the HMAC sent in the request matches the one generated
* on the panel side.
*
*
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
*/
protected function validateContents()
{
if (! hash_equals(base64_decode($this->hash()), $this->generateSignature())) {
throw new BadRequestHttpException('The HMAC for the request was invalid.');
}
}
/**
* Generate a HMAC from the request and known API secret key.
*
* @return string
*/
protected function generateSignature()
{
$content = urldecode($this->request()->fullUrl()) . $this->request()->getContent();
return hash_hmac(self::HMAC_ALGORITHM, $content, Crypt::decrypt($this->key()->secret), true);
}
/**
* Return the public key passed in the Authorization header.
*
* @return string
*/
protected function public()
{
return $this->token['public'];
}
/**
* Return the base64'd HMAC sent in the Authorization header.
*
* @return string
*/
protected function hash()
{
return $this->token['hash'];
}
/**
* Return the API Key model.
*
* @return \Pterodactyl\Models\APIKey
*/
protected function key()
{
return $this->key;
}
/**
* Return the Illuminate Request object.
*
* @return \Illuminate\Http\Request
*/
private function request()
{
return $this->request;
}
}