Add activity logging for files

This commit is contained in:
DaneEveritt 2022-05-29 13:56:39 -04:00
parent 0999ad7ff0
commit cbecfff6da
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
6 changed files with 96 additions and 106 deletions

View file

@ -5,10 +5,9 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Models\AuditLog;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Pterodactyl\Facades\Activity;
use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Services\Nodes\NodeJWTService;
use Illuminate\Contracts\Routing\ResponseFactory;
use Pterodactyl\Repositories\Wings\DaemonFileRepository; use Pterodactyl\Repositories\Wings\DaemonFileRepository;
use Pterodactyl\Transformers\Api\Client\FileObjectTransformer; use Pterodactyl\Transformers\Api\Client\FileObjectTransformer;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
@ -31,11 +30,6 @@ class FileController extends ClientApiController
*/ */
private $fileRepository; private $fileRepository;
/**
* @var \Illuminate\Contracts\Routing\ResponseFactory
*/
private $responseFactory;
/** /**
* @var \Pterodactyl\Services\Nodes\NodeJWTService * @var \Pterodactyl\Services\Nodes\NodeJWTService
*/ */
@ -45,14 +39,12 @@ class FileController extends ClientApiController
* FileController constructor. * FileController constructor.
*/ */
public function __construct( public function __construct(
ResponseFactory $responseFactory,
NodeJWTService $jwtService, NodeJWTService $jwtService,
DaemonFileRepository $fileRepository DaemonFileRepository $fileRepository
) { ) {
parent::__construct(); parent::__construct();
$this->fileRepository = $fileRepository; $this->fileRepository = $fileRepository;
$this->responseFactory = $responseFactory;
$this->jwtService = $jwtService; $this->jwtService = $jwtService;
} }
@ -84,6 +76,8 @@ class FileController extends ClientApiController
config('pterodactyl.files.max_edit_size') 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']); 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) public function download(GetFileContentsRequest $request, Server $server)
{ {
$token = $server->audit( $token = $this->jwtService
AuditLog::SERVER__FILESYSTEM_DOWNLOAD, ->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
function (AuditLog $audit, Server $server) use ($request) { ->setClaims([
$audit->metadata = ['file' => $request->get('file')]; 'file_path' => rawurldecode($request->get('file')),
'server_uuid' => $server->uuid,
])
->handle($server->node, $request->user()->id . $server->uuid);
return $this->jwtService Activity::event('server:file.download')->property('file', $request->get('file'))->log();
->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 [ return [
'object' => 'signed_url', 'object' => 'signed_url',
@ -131,14 +120,9 @@ class FileController extends ClientApiController
*/ */
public function write(WriteFileContentRequest $request, Server $server): JsonResponse public function write(WriteFileContentRequest $request, Server $server): JsonResponse
{ {
$server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { $this->fileRepository->setServer($server)->putContent($request->get('file'), $request->getContent());
$audit->subaction = 'write_content';
$audit->metadata = ['file' => $request->get('file')];
$this->fileRepository Activity::event('server:file.write')->property('file', $request->get('file'))->log();
->setServer($server)
->putContent($request->get('file'), $request->getContent());
});
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }
@ -150,14 +134,14 @@ class FileController extends ClientApiController
*/ */
public function create(CreateFolderRequest $request, Server $server): JsonResponse public function create(CreateFolderRequest $request, Server $server): JsonResponse
{ {
$server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { $this->fileRepository
$audit->subaction = 'create_folder'; ->setServer($server)
$audit->metadata = ['file' => $request->input('root', '/') . $request->input('name')]; ->createDirectory($request->input('name'), $request->input('root', '/'));
$this->fileRepository Activity::event('server:file.create-directory')
->setServer($server) ->property('name', $request->input('name'))
->createDirectory($request->input('name'), $request->input('root', '/')); ->property('directory', $request->input('root'))
}); ->log();
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }
@ -169,13 +153,14 @@ class FileController extends ClientApiController
*/ */
public function rename(RenameFileRequest $request, Server $server): JsonResponse public function rename(RenameFileRequest $request, Server $server): JsonResponse
{ {
$server->audit(AuditLog::SERVER__FILESYSTEM_RENAME, function (AuditLog $audit, Server $server) use ($request) { $this->fileRepository
$audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; ->setServer($server)
->renameFiles($request->input('root'), $request->input('files'));
$this->fileRepository Activity::event('server:file.rename')
->setServer($server) ->property('directory', $request->input('root'))
->renameFiles($request->input('root'), $request->input('files')); ->property('files', $request->input('files'))
}); ->log();
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }
@ -187,14 +172,11 @@ class FileController extends ClientApiController
*/ */
public function copy(CopyFileRequest $request, Server $server): JsonResponse public function copy(CopyFileRequest $request, Server $server): JsonResponse
{ {
$server->audit(AuditLog::SERVER__FILESYSTEM_WRITE, function (AuditLog $audit, Server $server) use ($request) { $this->fileRepository
$audit->subaction = 'copy_file'; ->setServer($server)
$audit->metadata = ['file' => $request->input('location')]; ->copyFile($request->input('location'));
$this->fileRepository Activity::event('server:file.copy')->property('file', $request->input('location'))->log();
->setServer($server)
->copyFile($request->input('location'));
});
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }
@ -204,22 +186,16 @@ class FileController extends ClientApiController
*/ */
public function compress(CompressFilesRequest $request, Server $server): array public function compress(CompressFilesRequest $request, Server $server): array
{ {
$file = $server->audit( $file = $this->fileRepository->setServer($server)->compressFiles(
AuditLog::SERVER__FILESYSTEM_COMPRESS, $request->input('root'),
function (AuditLog $audit, Server $server) use ($request) { $request->input('files')
// 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')
);
}
); );
Activity::event('server:file.compress')
->property('directory', $request->input('root'))
->property('files', $request->input('files'))
->log();
return $this->fractal->item($file) return $this->fractal->item($file)
->transformWith($this->getTransformer(FileObjectTransformer::class)) ->transformWith($this->getTransformer(FileObjectTransformer::class))
->toArray(); ->toArray();
@ -230,19 +206,18 @@ class FileController extends ClientApiController
*/ */
public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse
{ {
$file = $server->audit( set_time_limit(300);
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);
$audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('file')]; $this->fileRepository->setServer($server)->decompressFile(
$request->input('root'),
$this->fileRepository->setServer($server) $request->input('file')
->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); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
} }
@ -253,15 +228,15 @@ class FileController extends ClientApiController
*/ */
public function delete(DeleteFileRequest $request, Server $server): JsonResponse public function delete(DeleteFileRequest $request, Server $server): JsonResponse
{ {
$server->audit(AuditLog::SERVER__FILESYSTEM_DELETE, function (AuditLog $audit, Server $server) use ($request) { $this->fileRepository->setServer($server)->deleteFiles(
$audit->metadata = ['root' => $request->input('root'), 'files' => $request->input('files')]; $request->input('root'),
$request->input('files')
);
$this->fileRepository->setServer($server) Activity::event('server:file.delete')
->deleteFiles( ->property('directory', $request->input('root'))
$request->input('root'), ->property('files', $request->input('files'))
$request->input('files') ->log();
);
});
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }
@ -273,11 +248,10 @@ class FileController extends ClientApiController
*/ */
public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse
{ {
$this->fileRepository->setServer($server) $this->fileRepository->setServer($server)->chmodFiles(
->chmodFiles( $request->input('root'),
$request->input('root'), $request->input('files')
$request->input('files') );
);
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }
@ -289,17 +263,16 @@ class FileController extends ClientApiController
*/ */
public function pull(PullFileRequest $request, Server $server): JsonResponse public function pull(PullFileRequest $request, Server $server): JsonResponse
{ {
$server->audit(AuditLog::SERVER__FILESYSTEM_PULL, function (AuditLog $audit, Server $server) use ($request) { $this->fileRepository->setServer($server)->pull(
$audit->metadata = ['directory' => $request->input('directory'), 'url' => $request->input('url')]; $request->input('url'),
$request->input('directory'),
$request->safe(['filename', 'use_header', 'foreground'])
);
$this->fileRepository Activity::event('server:file.pull')
->setServer($server) ->property('directory', $request->input('directory'))
->pull( ->property('url', $request->input('url'))
$request->input('url'), ->log();
$request->input('directory'),
$request->safe(['filename', 'use_header', 'foreground'])
);
});
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }

View file

@ -6,6 +6,7 @@ use Carbon\CarbonImmutable;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Pterodactyl\Facades\Activity;
use Pterodactyl\Services\Nodes\NodeJWTService; use Pterodactyl\Services\Nodes\NodeJWTService;
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\UploadFileRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Files\UploadFileRequest;
@ -35,6 +36,8 @@ class FileUploadController extends ClientApiController
*/ */
public function __invoke(UploadFileRequest $request, Server $server) public function __invoke(UploadFileRequest $request, Server $server)
{ {
Activity::event('server:file.upload')->log();
return new JsonResponse([ return new JsonResponse([
'object' => 'signed_url', 'object' => 'signed_url',
'attributes' => [ 'attributes' => [

View file

@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Model as IlluminateModel;
* @property int $id * @property int $id
* @property string|null $batch * @property string|null $batch
* @property string $event * @property string $event
* @property string $ip
* @property string|null $description * @property string|null $description
* @property string|null $actor_type * @property string|null $actor_type
* @property int|null $actor_id * @property int|null $actor_id
@ -19,8 +20,8 @@ use Illuminate\Database\Eloquent\Model as IlluminateModel;
* @property int|null $subject_id * @property int|null $subject_id
* @property \Illuminate\Support\Collection $properties * @property \Illuminate\Support\Collection $properties
* @property string $timestamp * @property string $timestamp
* @property \Illuminate\Database\Eloquent\Model|\Eloquent $actor * @property IlluminateModel|\Eloquent $actor
* @property \Illuminate\Database\Eloquent\Model|\Eloquent $subject * @property IlluminateModel|\Eloquent $subject
* *
* @method static Builder|ActivityLog forAction(string $action) * @method static Builder|ActivityLog forAction(string $action)
* @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor) * @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 newModelQuery()
* @method static Builder|ActivityLog newQuery() * @method static Builder|ActivityLog newQuery()
* @method static Builder|ActivityLog query() * @method static Builder|ActivityLog query()
* @method static Builder|ActivityLog whereAction($value)
* @method static Builder|ActivityLog whereActorId($value) * @method static Builder|ActivityLog whereActorId($value)
* @method static Builder|ActivityLog whereActorType($value) * @method static Builder|ActivityLog whereActorType($value)
* @method static Builder|ActivityLog whereBatch($value) * @method static Builder|ActivityLog whereBatch($value)
* @method static Builder|ActivityLog whereDescription($value) * @method static Builder|ActivityLog whereDescription($value)
* @method static Builder|ActivityLog whereEvent($value)
* @method static Builder|ActivityLog whereId($value) * @method static Builder|ActivityLog whereId($value)
* @method static Builder|ActivityLog whereIp($value) * @method static Builder|ActivityLog whereIp($value)
* @method static Builder|ActivityLog whereProperties($value) * @method static Builder|ActivityLog whereProperties($value)
@ -57,8 +58,9 @@ class ActivityLog extends Model
public static $validationRules = [ public static $validationRules = [
'event' => ['required', 'string'], 'event' => ['required', 'string'],
'batch' => ['nullable', 'uuid'], 'batch' => ['nullable', 'uuid'],
'ip' => ['required', 'string'],
'description' => ['nullable', 'string'], 'description' => ['nullable', 'string'],
'properties' => ['nullable', 'array'], 'properties' => ['array'],
]; ];
public function actor(): MorphTo public function actor(): MorphTo

View file

@ -181,6 +181,7 @@ class ActivityLogService
} }
$this->activity = new ActivityLog([ $this->activity = new ActivityLog([
'ip' => Request::ip(),
'batch_uuid' => $this->batch->uuid(), 'batch_uuid' => $this->batch->uuid(),
'properties' => Collection::make([]), 'properties' => Collection::make([]),
]); ]);

View file

@ -17,10 +17,11 @@ class CreateActivityLogsTable extends Migration
$table->id(); $table->id();
$table->uuid('batch')->nullable(); $table->uuid('batch')->nullable();
$table->string('event')->index(); $table->string('event')->index();
$table->string('ip');
$table->text('description')->nullable(); $table->text('description')->nullable();
$table->nullableNumericMorphs('actor'); $table->nullableNumericMorphs('actor');
$table->nullableNumericMorphs('subject'); $table->nullableNumericMorphs('subject');
$table->json('properties')->nullable(); $table->json('properties');
$table->timestamp('timestamp')->useCurrent()->onUpdate(null); $table->timestamp('timestamp')->useCurrent()->onUpdate(null);
}); });
} }

View file

@ -2,6 +2,7 @@
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Pterodactyl\Http\Controllers\Api\Client; use Pterodactyl\Http\Controllers\Api\Client;
use Pterodactyl\Http\Middleware\ServerActivityLogs;
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
use Pterodactyl\Http\Middleware\Api\Client\Server\ResourceBelongsToServer; use Pterodactyl\Http\Middleware\Api\Client\Server\ResourceBelongsToServer;
use Pterodactyl\Http\Middleware\Api\Client\Server\AuthenticateServerAccess; 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::get('/permissions', [Client\ClientController::class, 'permissions']);
Route::group(['prefix' => '/account'], function () { Route::group(['prefix' => '/account'], function () {
Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account')->withoutMiddleware(RequireTwoFactorAuthentication::class); Route::prefix('/')->withoutMiddleware(RequireTwoFactorAuthentication::class)->group(function () {
Route::get('/two-factor', [Client\TwoFactorController::class, 'index'])->withoutMiddleware(RequireTwoFactorAuthentication::class); Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account');
Route::post('/two-factor', [Client\TwoFactorController::class, 'store'])->withoutMiddleware(RequireTwoFactorAuthentication::class); Route::get('/two-factor', [Client\TwoFactorController::class, 'index']);
Route::delete('/two-factor', [Client\TwoFactorController::class, 'delete'])->withoutMiddleware(RequireTwoFactorAuthentication::class); 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('/email', [Client\AccountController::class, 'updateEmail'])->name('api:client.account.update-email');
Route::put('/password', [Client\AccountController::class, 'updatePassword'])->name('api:client.account.update-password'); 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} | 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('/', [Client\Servers\ServerController::class, 'index'])->name('api:client:server.view');
Route::get('/websocket', Client\Servers\WebsocketController::class)->name('api:client:server.ws'); Route::get('/websocket', Client\Servers\WebsocketController::class)->name('api:client:server.ws');
Route::get('/resources', Client\Servers\ResourceUtilizationController::class)->name('api:client:server.resources'); Route::get('/resources', Client\Servers\ResourceUtilizationController::class)->name('api:client:server.resources');