diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index 7e5ff45ee..c2856ff09 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -5,10 +5,9 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Carbon\CarbonImmutable; use Illuminate\Http\Response; use Pterodactyl\Models\Server; -use Pterodactyl\Models\AuditLog; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Nodes\NodeJWTService; -use Illuminate\Contracts\Routing\ResponseFactory; use Pterodactyl\Repositories\Wings\DaemonFileRepository; use Pterodactyl\Transformers\Api\Client\FileObjectTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -31,11 +30,6 @@ class FileController extends ClientApiController */ private $fileRepository; - /** - * @var \Illuminate\Contracts\Routing\ResponseFactory - */ - private $responseFactory; - /** * @var \Pterodactyl\Services\Nodes\NodeJWTService */ @@ -45,14 +39,12 @@ class FileController extends ClientApiController * FileController constructor. */ public function __construct( - ResponseFactory $responseFactory, NodeJWTService $jwtService, DaemonFileRepository $fileRepository ) { parent::__construct(); $this->fileRepository = $fileRepository; - $this->responseFactory = $responseFactory; $this->jwtService = $jwtService; } @@ -84,6 +76,8 @@ class FileController extends ClientApiController config('pterodactyl.files.max_edit_size') ); + Activity::event('server:file.read')->property('file', $request->get('file'))->log(); + return new Response($response, Response::HTTP_OK, ['Content-Type' => 'text/plain']); } @@ -97,20 +91,15 @@ class FileController extends ClientApiController */ public function download(GetFileContentsRequest $request, Server $server) { - $token = $server->audit( - AuditLog::SERVER__FILESYSTEM_DOWNLOAD, - function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['file' => $request->get('file')]; + $token = $this->jwtService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setClaims([ + 'file_path' => rawurldecode($request->get('file')), + 'server_uuid' => $server->uuid, + ]) + ->handle($server->node, $request->user()->id . $server->uuid); - return $this->jwtService - ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) - ->setClaims([ - 'file_path' => rawurldecode($request->get('file')), - 'server_uuid' => $server->uuid, - ]) - ->handle($server->node, $request->user()->id . $server->uuid); - } - ); + Activity::event('server:file.download')->property('file', $request->get('file'))->log(); return [ 'object' => 'signed_url', @@ -131,14 +120,9 @@ class FileController extends ClientApiController */ public function write(WriteFileContentRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { - $audit->subaction = 'write_content'; - $audit->metadata = ['file' => $request->get('file')]; + $this->fileRepository->setServer($server)->putContent($request->get('file'), $request->getContent()); - $this->fileRepository - ->setServer($server) - ->putContent($request->get('file'), $request->getContent()); - }); + Activity::event('server:file.write')->property('file', $request->get('file'))->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -150,14 +134,14 @@ class FileController extends ClientApiController */ public function create(CreateFolderRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { - $audit->subaction = 'create_folder'; - $audit->metadata = ['file' => $request->input('root', '/') . $request->input('name')]; + $this->fileRepository + ->setServer($server) + ->createDirectory($request->input('name'), $request->input('root', '/')); - $this->fileRepository - ->setServer($server) - ->createDirectory($request->input('name'), $request->input('root', '/')); - }); + Activity::event('server:file.create-directory') + ->property('name', $request->input('name')) + ->property('directory', $request->input('root')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -169,13 +153,14 @@ class FileController extends ClientApiController */ public function rename(RenameFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_RENAME, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; + $this->fileRepository + ->setServer($server) + ->renameFiles($request->input('root'), $request->input('files')); - $this->fileRepository - ->setServer($server) - ->renameFiles($request->input('root'), $request->input('files')); - }); + Activity::event('server:file.rename') + ->property('directory', $request->input('root')) + ->property('files', $request->input('files')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -187,14 +172,11 @@ class FileController extends ClientApiController */ public function copy(CopyFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { - $audit->subaction = 'copy_file'; - $audit->metadata = ['file' => $request->input('location')]; + $this->fileRepository + ->setServer($server) + ->copyFile($request->input('location')); - $this->fileRepository - ->setServer($server) - ->copyFile($request->input('location')); - }); + Activity::event('server:file.copy')->property('file', $request->input('location'))->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -204,22 +186,16 @@ class FileController extends ClientApiController */ public function compress(CompressFilesRequest $request, Server $server): array { - $file = $server->audit( - AuditLog::SERVER__FILESYSTEM_COMPRESS, - function (AuditLog $audit, Server $server) use ($request) { - // Allow up to five minutes for this request to process before timing out. - set_time_limit(300); - - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; - - return $this->fileRepository->setServer($server) - ->compressFiles( - $request->input('root'), - $request->input('files') - ); - } + $file = $this->fileRepository->setServer($server)->compressFiles( + $request->input('root'), + $request->input('files') ); + Activity::event('server:file.compress') + ->property('directory', $request->input('root')) + ->property('files', $request->input('files')) + ->log(); + return $this->fractal->item($file) ->transformWith($this->getTransformer(FileObjectTransformer::class)) ->toArray(); @@ -230,19 +206,18 @@ class FileController extends ClientApiController */ public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse { - $file = $server->audit( - AuditLog::SERVER__FILESYSTEM_DECOMPRESS, - function (AuditLog $audit, Server $server) use ($request) { - // Allow up to five minutes for this request to process before timing out. - set_time_limit(300); + set_time_limit(300); - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('file')]; - - $this->fileRepository->setServer($server) - ->decompressFile($request->input('root'), $request->input('file')); - } + $this->fileRepository->setServer($server)->decompressFile( + $request->input('root'), + $request->input('file') ); + Activity::event('server:file.decompress') + ->property('directory', $request->input('root')) + ->property('files', $request->input('file')) + ->log(); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } @@ -253,15 +228,15 @@ class FileController extends ClientApiController */ public function delete(DeleteFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_DELETE, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; + $this->fileRepository->setServer($server)->deleteFiles( + $request->input('root'), + $request->input('files') + ); - $this->fileRepository->setServer($server) - ->deleteFiles( - $request->input('root'), - $request->input('files') - ); - }); + Activity::event('server:file.delete') + ->property('directory', $request->input('root')) + ->property('files', $request->input('files')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -273,11 +248,10 @@ class FileController extends ClientApiController */ public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse { - $this->fileRepository->setServer($server) - ->chmodFiles( - $request->input('root'), - $request->input('files') - ); + $this->fileRepository->setServer($server)->chmodFiles( + $request->input('root'), + $request->input('files') + ); return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -289,17 +263,16 @@ class FileController extends ClientApiController */ public function pull(PullFileRequest $request, Server $server): JsonResponse { - $server->audit(AuditLog::SERVER__FILESYSTEM_PULL, function (AuditLog $audit, Server $server) use ($request) { - $audit->metadata = ['directory' => $request->input('directory'), 'url' => $request->input('url')]; + $this->fileRepository->setServer($server)->pull( + $request->input('url'), + $request->input('directory'), + $request->safe(['filename', 'use_header', 'foreground']) + ); - $this->fileRepository - ->setServer($server) - ->pull( - $request->input('url'), - $request->input('directory'), - $request->safe(['filename', 'use_header', 'foreground']) - ); - }); + Activity::event('server:file.pull') + ->property('directory', $request->input('directory')) + ->property('url', $request->input('url')) + ->log(); return new JsonResponse([], Response::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Client/Servers/FileUploadController.php b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php index 236273c75..a3b362520 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileUploadController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileUploadController.php @@ -6,6 +6,7 @@ use Carbon\CarbonImmutable; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\UploadFileRequest; @@ -35,6 +36,8 @@ class FileUploadController extends ClientApiController */ public function __invoke(UploadFileRequest $request, Server $server) { + Activity::event('server:file.upload')->log(); + return new JsonResponse([ 'object' => 'signed_url', 'attributes' => [ diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php index f58d827c6..47ef911ca 100644 --- a/app/Models/ActivityLog.php +++ b/app/Models/ActivityLog.php @@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Model as IlluminateModel; * @property int $id * @property string|null $batch * @property string $event + * @property string $ip * @property string|null $description * @property string|null $actor_type * @property int|null $actor_id @@ -19,8 +20,8 @@ use Illuminate\Database\Eloquent\Model as IlluminateModel; * @property int|null $subject_id * @property \Illuminate\Support\Collection $properties * @property string $timestamp - * @property \Illuminate\Database\Eloquent\Model|\Eloquent $actor - * @property \Illuminate\Database\Eloquent\Model|\Eloquent $subject + * @property IlluminateModel|\Eloquent $actor + * @property IlluminateModel|\Eloquent $subject * * @method static Builder|ActivityLog forAction(string $action) * @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor) @@ -28,11 +29,11 @@ use Illuminate\Database\Eloquent\Model as IlluminateModel; * @method static Builder|ActivityLog newModelQuery() * @method static Builder|ActivityLog newQuery() * @method static Builder|ActivityLog query() - * @method static Builder|ActivityLog whereAction($value) * @method static Builder|ActivityLog whereActorId($value) * @method static Builder|ActivityLog whereActorType($value) * @method static Builder|ActivityLog whereBatch($value) * @method static Builder|ActivityLog whereDescription($value) + * @method static Builder|ActivityLog whereEvent($value) * @method static Builder|ActivityLog whereId($value) * @method static Builder|ActivityLog whereIp($value) * @method static Builder|ActivityLog whereProperties($value) @@ -57,8 +58,9 @@ class ActivityLog extends Model public static $validationRules = [ 'event' => ['required', 'string'], 'batch' => ['nullable', 'uuid'], + 'ip' => ['required', 'string'], 'description' => ['nullable', 'string'], - 'properties' => ['nullable', 'array'], + 'properties' => ['array'], ]; public function actor(): MorphTo diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index 42293b7e4..8ad5af53b 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -181,6 +181,7 @@ class ActivityLogService } $this->activity = new ActivityLog([ + 'ip' => Request::ip(), 'batch_uuid' => $this->batch->uuid(), 'properties' => Collection::make([]), ]); diff --git a/database/migrations/2022_05_28_135717_create_activity_logs_table.php b/database/migrations/2022_05_28_135717_create_activity_logs_table.php index 570cc4060..0624a5a66 100644 --- a/database/migrations/2022_05_28_135717_create_activity_logs_table.php +++ b/database/migrations/2022_05_28_135717_create_activity_logs_table.php @@ -17,10 +17,11 @@ class CreateActivityLogsTable extends Migration $table->id(); $table->uuid('batch')->nullable(); $table->string('event')->index(); + $table->string('ip'); $table->text('description')->nullable(); $table->nullableNumericMorphs('actor'); $table->nullableNumericMorphs('subject'); - $table->json('properties')->nullable(); + $table->json('properties'); $table->timestamp('timestamp')->useCurrent()->onUpdate(null); }); } diff --git a/routes/api-client.php b/routes/api-client.php index 5c2bfa9b1..b592ea420 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -2,6 +2,7 @@ use Illuminate\Support\Facades\Route; use Pterodactyl\Http\Controllers\Api\Client; +use Pterodactyl\Http\Middleware\ServerActivityLogs; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Pterodactyl\Http\Middleware\Api\Client\Server\ResourceBelongsToServer; use Pterodactyl\Http\Middleware\Api\Client\Server\AuthenticateServerAccess; @@ -18,10 +19,12 @@ Route::get('/', [Client\ClientController::class, 'index'])->name('api:client.ind Route::get('/permissions', [Client\ClientController::class, 'permissions']); Route::group(['prefix' => '/account'], function () { - Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account')->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::get('/two-factor', [Client\TwoFactorController::class, 'index'])->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::post('/two-factor', [Client\TwoFactorController::class, 'store'])->withoutMiddleware(RequireTwoFactorAuthentication::class); - Route::delete('/two-factor', [Client\TwoFactorController::class, 'delete'])->withoutMiddleware(RequireTwoFactorAuthentication::class); + Route::prefix('/')->withoutMiddleware(RequireTwoFactorAuthentication::class)->group(function () { + Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account'); + Route::get('/two-factor', [Client\TwoFactorController::class, 'index']); + Route::post('/two-factor', [Client\TwoFactorController::class, 'store']); + Route::delete('/two-factor', [Client\TwoFactorController::class, 'delete']); + }); Route::put('/email', [Client\AccountController::class, 'updateEmail'])->name('api:client.account.update-email'); Route::put('/password', [Client\AccountController::class, 'updatePassword'])->name('api:client.account.update-password'); @@ -45,7 +48,14 @@ Route::group(['prefix' => '/account'], function () { | Endpoint: /api/client/servers/{server} | */ -Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServerAccess::class, ResourceBelongsToServer::class]], function () { +Route::group([ + 'prefix' => '/servers/{server}', + 'middleware' => [ + ServerActivityLogs::class, + AuthenticateServerAccess::class, + ResourceBelongsToServer::class, + ], +], function () { Route::get('/', [Client\Servers\ServerController::class, 'index'])->name('api:client:server.view'); Route::get('/websocket', Client\Servers\WebsocketController::class)->name('api:client:server.ws'); Route::get('/resources', Client\Servers\ResourceUtilizationController::class)->name('api:client:server.resources');