diff --git a/app/Http/Controllers/API/AuthController.php b/app/Http/Controllers/API/AuthController.php deleted file mode 100644 index 2a4284a10..000000000 --- a/app/Http/Controllers/API/AuthController.php +++ /dev/null @@ -1,124 +0,0 @@ -"}) - */ - public function postLogin(Request $request) { - - $validator = Validator::make($request->only(['email', 'password']), [ - 'email' => 'required|email', - 'password' => 'required|min:8' - ]); - - if ($validator->fails()) { - throw new StoreResourceFailedException('Required authentication fields were invalid.', $validator->errors()); - } - - $throttled = $this->isUsingThrottlesLoginsTrait(); - if ($throttled && $this->hasTooManyLoginAttempts($request)) { - throw new TooManyRequestsHttpException('You have been login throttled for 120 seconds.'); - } - - // Is the email & password valid? - $user = Models\User::where('email', $request->input('email'))->first(); - if (!$user || !Hash::check($request->input('password'), $user->password)) { - if ($throttled) { - $this->incrementLoginAttempts($request); - } - throw new UnauthorizedHttpException('A user by those credentials was not found.'); - } - - // @TODO: validate TOTP if enabled on account? - // Perhaps this could be implemented in such a way that they login to their - // account and generate a one time password that can be used? Would be a pain in - // the butt for multiple API requests though. Maybe just included a 'totp' field - // that can include the token for that timestamp. Would allow for programtic - // generation of the code and API requests. - if ($user->root_admin !== 1) { - throw new UnauthorizedHttpException('This account does not have permission to interface this API.'); - } - - try { - $token = JWTAuth::fromUser($user); - if (!$token) { - throw new UnauthorizedHttpException(''); - } - } catch (JWTException $ex) { - throw new ServiceUnavailableHttpException(''); - } - - return compact('token'); - } - - /** - * Check if Authenticated - * - * @Post("/auth/validate") - * @Versions({"v1"}) - * @Request(headers={"Authorization": "Bearer "}) - * @Response(204) - */ - public function postValidate(Request $request) { - return $this->response->noContent(); - } - -} diff --git a/app/Http/Middleware/APISecretToken.php b/app/Http/Middleware/APISecretToken.php new file mode 100644 index 000000000..7678a1e89 --- /dev/null +++ b/app/Http/Middleware/APISecretToken.php @@ -0,0 +1,80 @@ +bearerToken() || empty($request->bearerToken())) { + throw new UnauthorizedHttpException('The authentication header was missing or malformed'); + } + + list($public, $hashed) = explode('.', $request->bearerToken()); + + $key = APIKey::where('public', $public)->first(); + if (!$key) { + throw new AccessDeniedHttpException('Invalid API Key.'); + } + + // Check for Resource Permissions + if (!empty($request->route()->getName())) { + if(!is_null($key->allowed_ips)) { + if (!in_array($request->ip(), json_decode($key->allowed_ips))) { + throw new AccessDeniedHttpException('This IP address does not have permission to use this API key.'); + } + } + + foreach(APIPermission::where('key_id', $key->id)->get() as &$row) { + if ($row->permission === '*' || $row->permission === $request->route()->getName()) { + $this->permissionAllowed = true; + continue; + } + } + + if (!$this->permissionAllowed) { + throw new AccessDeniedHttpException('You do not have permission to access this resource.'); + } + } + + if($this->_generateHMAC($request->fullUrl(), $request->getContent(), $key->secret) !== base64_decode($hashed)) { + throw new BadRequestHttpException('The hashed body was not valid. Potential modification of contents in route.'); + } + + return true; + + } + + protected function _generateHMAC($url, $body, $key) + { + $data = urldecode($url) . '.' . $body; + return hash_hmac($this->algo, $data, $key, true); + } + +} diff --git a/app/Http/Routes/APIRoutes.php b/app/Http/Routes/APIRoutes.php index 3c1233ff2..a0e715cb5 100644 --- a/app/Http/Routes/APIRoutes.php +++ b/app/Http/Routes/APIRoutes.php @@ -10,25 +10,7 @@ class APIRoutes public function map(Router $router) { - app('Dingo\Api\Auth\Auth')->extend('jwt', function ($app) { - return new \Dingo\Api\Auth\Provider\JWT($app['Tymon\JWTAuth\JWTAuth']); - }); - $api = app('Dingo\Api\Routing\Router'); - - $api->version('v1', function ($api) { - $api->post('auth/login', [ - 'as' => 'api.auth.login', - 'uses' => 'Pterodactyl\Http\Controllers\API\AuthController@postLogin' - ]); - - $api->post('auth/validate', [ - 'middleware' => 'api.auth', - 'as' => 'api.auth.validate', - 'uses' => 'Pterodactyl\Http\Controllers\API\AuthController@postValidate' - ]); - }); - $api->version('v1', ['middleware' => 'api.auth'], function ($api) { /** diff --git a/app/Models/API.php b/app/Models/API.php deleted file mode 100644 index b33985252..000000000 --- a/app/Models/API.php +++ /dev/null @@ -1,63 +0,0 @@ -hasMany(APIPermission::class); - } - - public static function findKey($key) - { - return self::where('key', $key)->first(); - } - - /** - * Determine if an API key has permission to perform an action. - * - * @param string $key - * @param string $permission - * @return boolean - */ - public static function checkPermission($key, $permission) - { - $api = self::findKey($key); - - if (!$api) { - throw new DisplayException('The requested API key (' . $key . ') was not found in the system.'); - } - - return APIPermission::check($api->id, $permission); - - } - - public static function noPermissionError($error = 'You do not have permission to perform this action with this API key.') - { - return response()->json([ - 'error' => 'You do not have permission to perform this action with this API key.' - ], 403); - } - -} diff --git a/app/Models/APIKey.php b/app/Models/APIKey.php new file mode 100644 index 000000000..dc7912f26 --- /dev/null +++ b/app/Models/APIKey.php @@ -0,0 +1,17 @@ +where('permission', $permission)->exists(); - } - } diff --git a/config/api.php b/config/api.php index 872723046..234a8bca7 100644 --- a/config/api.php +++ b/config/api.php @@ -155,7 +155,7 @@ return [ */ 'auth' => [ - 'jwt' => 'Dingo\Api\Auth\Provider\JWT' + 'custom' => 'Pterodactyl\Http\Middleware\APISecretToken' ], /* diff --git a/database/migrations/2016_01_15_231502_create_table_api_keys.php b/database/migrations/2016_01_15_231502_create_table_api_keys.php new file mode 100644 index 000000000..9b1fb4bb7 --- /dev/null +++ b/database/migrations/2016_01_15_231502_create_table_api_keys.php @@ -0,0 +1,33 @@ +increments('id'); + $table->char('public', 16); + $table->char('secret', 32); + $table->json('allowed_ips')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('api_keys'); + } +} diff --git a/database/migrations/2016_01_15_233110_create_table_api_permissions.php b/database/migrations/2016_01_15_233110_create_table_api_permissions.php new file mode 100644 index 000000000..9056003b9 --- /dev/null +++ b/database/migrations/2016_01_15_233110_create_table_api_permissions.php @@ -0,0 +1,31 @@ +increments('id'); + $table->integer('key_id')->unsigned(); + $table->string('permission'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('api_permissions'); + } +}