From af68dbed8f05ea3ae79ca1b28128a0e8cb832ee7 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 7 Oct 2016 16:06:09 -0400 Subject: [PATCH] Add support for base API logging of all requests ref #31 --- app/Http/Middleware/APISecretToken.php | 9 +++ app/Models/APILog.php | 61 ++++++++++++++++++ app/Services/APILogService.php | 63 +++++++++++++++++++ .../2016_10_07_152117_build_api_log_table.php | 39 ++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 app/Models/APILog.php create mode 100644 app/Services/APILogService.php create mode 100644 database/migrations/2016_10_07_152117_build_api_log_table.php diff --git a/app/Http/Middleware/APISecretToken.php b/app/Http/Middleware/APISecretToken.php index 71c148986..874ec7c29 100755 --- a/app/Http/Middleware/APISecretToken.php +++ b/app/Http/Middleware/APISecretToken.php @@ -29,6 +29,7 @@ use IPTools\Range; use Pterodactyl\Models\APIKey; use Pterodactyl\Models\APIPermission; +use Pterodactyl\Services\APILogService; use Illuminate\Http\Request; use Dingo\Api\Routing\Route; @@ -61,6 +62,7 @@ class APISecretToken extends Authorization public function authenticate(Request $request, Route $route) { if (!$request->bearerToken() || empty($request->bearerToken())) { + APILogService::log($request); throw new UnauthorizedHttpException('The authentication header was missing or malformed'); } @@ -68,6 +70,7 @@ class APISecretToken extends Authorization $key = APIKey::where('public', $public)->first(); if (!$key) { + APILogService::log($request); throw new AccessDeniedHttpException('Invalid API Key.'); } @@ -82,6 +85,7 @@ class APISecretToken extends Authorization } } if (!$inRange) { + APILogService::log($request); throw new AccessDeniedHttpException('This IP address <' . $request->ip() . '> does not have permission to use this API key.'); } } @@ -94,6 +98,7 @@ class APISecretToken extends Authorization } if (!$this->permissionAllowed) { + APILogService::log($request); throw new AccessDeniedHttpException('You do not have permission to access this resource.'); } } @@ -101,14 +106,18 @@ class APISecretToken extends Authorization try { $decrypted = Crypt::decrypt($key->secret); } catch (\Illuminate\Contracts\Encryption\DecryptException $ex) { + APILogService::log($request); throw new HttpException('There was an error while attempting to check your secret key.'); } $this->url = urldecode($request->fullUrl()); if($this->_generateHMAC($request->getContent(), $decrypted) !== base64_decode($hashed)) { + APILogService::log($request); throw new BadRequestHttpException('The hashed body was not valid. Potential modification of contents in route.'); } + // Log the Route Access + APILogService::log($request, true); return true; } diff --git a/app/Models/APILog.php b/app/Models/APILog.php new file mode 100644 index 000000000..41a7f8e47 --- /dev/null +++ b/app/Models/APILog.php @@ -0,0 +1,61 @@ + + * + * 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\Models; + +use Illuminate\Database\Eloquent\Model; + +class APILog extends Model +{ + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'api_logs'; + + /** + * The attributes excluded from the model's JSON form. + * + * @var array + */ + protected $hidden = []; + + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'authorized' => 'boolean' + ]; + +} diff --git a/app/Services/APILogService.php b/app/Services/APILogService.php new file mode 100644 index 000000000..d2eab8c50 --- /dev/null +++ b/app/Services/APILogService.php @@ -0,0 +1,63 @@ + + * + * 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\Services; + +use Log; + +use Illuminate\Http\Request; +use Pterodactyl\Models\APILog; + +class APILogService +{ + + public function __constructor() + { + // + } + + public static function log(Request $request, $authorized = false) + { + if ($request->bearerToken() && !empty($request->bearerToken())) { + list($public, $hashed) = explode('.', $request->bearerToken()); + } else { + $public = null; + } + + try { + $log = APILog::create([ + 'authorized' => $authorized, + 'key' => $public, + 'method' => $request->method(), + 'route' => $request->fullUrl(), + 'content' => (empty($request->getContent())) ? null : $request->getContent(), + 'user_agent' => $request->header('User-Agent'), + 'request_ip' => $request->ip() + ]); + $log->save(); + } catch (\Exception $ex) { + // Simply log it and move on. + Log::error($ex); + } + } +} diff --git a/database/migrations/2016_10_07_152117_build_api_log_table.php b/database/migrations/2016_10_07_152117_build_api_log_table.php new file mode 100644 index 000000000..9e9ae40e7 --- /dev/null +++ b/database/migrations/2016_10_07_152117_build_api_log_table.php @@ -0,0 +1,39 @@ +increments('id'); + $table->boolean('authorized'); + $table->char('key', 16)->nullable(); + $table->char('method', 6); + $table->text('route'); + $table->text('content')->nullable(); + $table->text('user_agent'); + $table->ipAddress('request_ip'); + $table->timestampsTz(); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('api_logs'); + } +}