diff --git a/app/Facades/Activity.php b/app/Facades/Activity.php index 86e2bd31c..febeccd8d 100644 --- a/app/Facades/Activity.php +++ b/app/Facades/Activity.php @@ -16,6 +16,7 @@ use Pterodactyl\Services\Activity\ActivityLogService; * @method static ActivityLogService property(string|array $key, mixed $value = null) * @method static \Pterodactyl\Models\ActivityLog log(string $description = null) * @method static ActivityLogService clone() + * @method static void reset() * @method static mixed transaction(\Closure $callback) */ class Activity extends Facade diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index 4f9aed59d..dc9186640 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; +use Pterodactyl\Facades\Activity; use Pterodactyl\Repositories\Eloquent\DatabaseRepository; use Pterodactyl\Services\Databases\DatabasePasswordService; use Pterodactyl\Transformers\Api\Client\DatabaseTransformer; @@ -76,6 +77,11 @@ class DatabaseController extends ClientApiController { $database = $this->deployDatabaseService->handle($server, $request->validated()); + Activity::event('server:database.create') + ->subject($database) + ->property('name', $database->database) + ->log(); + return $this->fractal->item($database) ->parseIncludes(['password']) ->transformWith($this->getTransformer(DatabaseTransformer::class)) @@ -95,6 +101,8 @@ class DatabaseController extends ClientApiController $this->passwordService->handle($database); $database->refresh(); + Activity::event('server:database.rotate-password')->subject($database)->log(); + return $this->fractal->item($database) ->parseIncludes(['password']) ->transformWith($this->getTransformer(DatabaseTransformer::class)) @@ -110,6 +118,11 @@ class DatabaseController extends ClientApiController { $this->managementService->delete($database); + Activity::event('server:database.delete') + ->subject($database) + ->property('name', $database->database) + ->log(); + return Response::create('', Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index a24dcab91..80455e0f8 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -4,6 +4,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Allocation; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Repositories\Eloquent\ServerRepository; @@ -68,9 +69,16 @@ class NetworkAllocationController extends ClientApiController */ public function update(UpdateAllocationRequest $request, Server $server, Allocation $allocation): array { - $allocation = $this->repository->update($allocation->id, [ - 'notes' => $request->input('notes'), - ]); + $original = $allocation->notes; + + $allocation->forceFill(['notes' => $request->input('notes')])->save(); + + if ($original !== $allocation->notes) { + Activity::event('server:allocation.notes') + ->subject($allocation) + ->property(['allocation' => $allocation->toString(), 'old' => $original, 'new' => $allocation->notes]) + ->log(); + } return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) @@ -87,6 +95,11 @@ class NetworkAllocationController extends ClientApiController { $this->serverRepository->update($server->id, ['allocation_id' => $allocation->id]); + Activity::event('server:allocation.primary') + ->subject($allocation) + ->property('allocation', $allocation->toString()) + ->log(); + return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) ->toArray(); @@ -106,6 +119,11 @@ class NetworkAllocationController extends ClientApiController $allocation = $this->assignableAllocationService->handle($server); + Activity::event('server:allocation.create') + ->subject($allocation) + ->property('allocation', $allocation->toString()) + ->log(); + return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) ->toArray(); @@ -135,6 +153,11 @@ class NetworkAllocationController extends ClientApiController 'server_id' => null, ]); + Activity::event('server:allocation.delete') + ->subject($allocation) + ->property('allocation', $allocation->toString()) + ->log(); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index 3e9b822bb..eb92bb002 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -9,6 +9,7 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Helpers\Utilities; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Repositories\Eloquent\ScheduleRepository; @@ -83,6 +84,11 @@ class ScheduleController extends ClientApiController 'next_run_at' => $this->getNextRunAt($request), ]); + Activity::event('server:schedule.create') + ->subject($model) + ->property('name', $model->name) + ->log(); + return $this->fractal->item($model) ->transformWith($this->getTransformer(ScheduleTransformer::class)) ->toArray(); @@ -141,6 +147,11 @@ class ScheduleController extends ClientApiController $this->repository->update($schedule->id, $data); + Activity::event('server:schedule.update') + ->subject($schedule) + ->property(['name' => $schedule->name, 'active' => $active]) + ->log(); + return $this->fractal->item($schedule->refresh()) ->transformWith($this->getTransformer(ScheduleTransformer::class)) ->toArray(); @@ -158,6 +169,8 @@ class ScheduleController extends ClientApiController { $this->service->handle($schedule, true); + Activity::event('server:schedule.execute')->subject($schedule)->property('name', $schedule->name)->log(); + return new JsonResponse([], JsonResponse::HTTP_ACCEPTED); } @@ -170,6 +183,8 @@ class ScheduleController extends ClientApiController { $this->repository->delete($schedule->id); + Activity::event('server:schedule.delete')->subject($schedule)->property('name', $schedule->name)->log(); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index 4ceed6550..75bb48d82 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -7,6 +7,7 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Permission; use Pterodactyl\Repositories\Eloquent\TaskRepository; use Pterodactyl\Exceptions\Http\HttpForbiddenException; @@ -67,6 +68,11 @@ class ScheduleTaskController extends ClientApiController 'continue_on_failure' => (bool) $request->input('continue_on_failure'), ]); + Activity::event('server:task.create') + ->subject($schedule, $task) + ->property(['name' => $schedule->name, 'action' => $task->action, 'payload' => $task->payload]) + ->log(); + return $this->fractal->item($task) ->transformWith($this->getTransformer(TaskTransformer::class)) ->toArray(); @@ -98,6 +104,11 @@ class ScheduleTaskController extends ClientApiController 'continue_on_failure' => (bool) $request->input('continue_on_failure'), ]); + Activity::event('server:task.update') + ->subject($schedule, $task) + ->property(['name' => $schedule->name, 'action' => $task->action, 'payload' => $task->payload]) + ->log(); + return $this->fractal->item($task->refresh()) ->transformWith($this->getTransformer(TaskTransformer::class)) ->toArray(); @@ -127,6 +138,8 @@ class ScheduleTaskController extends ClientApiController $task->delete(); + Activity::event('server:task.delete')->subject($schedule, $task)->property('name', $schedule->name)->log(); + return new JsonResponse(null, Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index a7fbb8a75..883362b79 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Services\Servers\ReinstallServerService; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; @@ -52,6 +53,12 @@ class SettingsController extends ClientApiController 'name' => $request->input('name'), ]); + if ($server->name !== $request->input('name')) { + Activity::event('server:settings.rename') + ->property(['old' => $server->name, 'new' => $request->input('name')]) + ->log(); + } + return new JsonResponse([], Response::HTTP_NO_CONTENT); } @@ -66,6 +73,8 @@ class SettingsController extends ClientApiController { $this->reinstallServerService->handle($server); + Activity::event('server:reinstall')->log(); + return new JsonResponse([], Response::HTTP_ACCEPTED); } @@ -82,8 +91,15 @@ class SettingsController extends ClientApiController throw new BadRequestHttpException('This server\'s Docker image has been manually set by an administrator and cannot be updated.'); } + $original = $server->image; $server->forceFill(['image' => $request->input('docker_image')])->saveOrFail(); + if ($original !== $server->image) { + Activity::event('server:startup.image') + ->property(['old' => $original, 'new' => $request->input('docker_image')]) + ->log(); + } + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index 06b4a5066..78194affd 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Pterodactyl\Models\Server; +use Pterodactyl\Facades\Activity; use Pterodactyl\Services\Servers\StartupCommandService; use Pterodactyl\Services\Servers\VariableValidatorService; use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; @@ -75,6 +76,7 @@ class StartupController extends ClientApiController { /** @var \Pterodactyl\Models\EggVariable $variable */ $variable = $server->variables()->where('env_variable', $request->input('key'))->first(); + $original = $variable->server_value; if (is_null($variable) || !$variable->user_viewable) { throw new BadRequestHttpException('The environment variable you are trying to edit does not exist.'); @@ -97,6 +99,17 @@ class StartupController extends ClientApiController $startup = $this->startupCommandService->handle($server, false); + if ($variable->env_variable !== $request->input('value')) { + Activity::event('server:startup.edit') + ->subject($variable) + ->property([ + 'variable' => $variable->env_variable, + 'old' => $original, + 'new' => $request->input('value'), + ]) + ->log(); + } + return $this->fractal->item($variable) ->transformWith($this->getTransformer(EggVariableTransformer::class)) ->addMeta([ diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index 68f32cf8b..ab7e5003a 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; use Illuminate\Http\Request; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Pterodactyl\Facades\Activity; use Pterodactyl\Models\Permission; use Illuminate\Support\Facades\Log; use Pterodactyl\Repositories\Eloquent\SubuserRepository; @@ -94,6 +95,11 @@ class SubuserController extends ClientApiController $this->getDefaultPermissions($request) ); + Activity::event('server:subuser.create') + ->subject($response->user) + ->property(['email' => $request->input('email'), 'permissions' => $this->getDefaultPermissions($request)]) + ->log(); + return $this->fractal->item($response) ->transformWith($this->getTransformer(SubuserTransformer::class)) ->toArray(); @@ -116,22 +122,37 @@ class SubuserController extends ClientApiController sort($permissions); sort($current); + $log = Activity::event('server:subuser.update') + ->subject($subuser->user) + ->property([ + 'email' => $subuser->user->email, + 'old' => $current, + 'new' => $permissions, + 'revoked' => true, + ]); + // Only update the database and hit up the Wings instance to invalidate JTI's if the permissions // have actually changed for the user. if ($permissions !== $current) { - $this->repository->update($subuser->id, [ - 'permissions' => $this->getDefaultPermissions($request), - ]); + $log->transaction(function ($instance) use ($request, $subuser, $server) { + $this->repository->update($subuser->id, [ + 'permissions' => $this->getDefaultPermissions($request), + ]); - try { - $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); - } catch (DaemonConnectionException $exception) { - // Don't block this request if we can't connect to the Wings instance. Chances are it is - // offline in this event and the token will be invalid anyways once Wings boots back. - Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); - } + try { + $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); + } catch (DaemonConnectionException $exception) { + // Don't block this request if we can't connect to the Wings instance. Chances are it is + // offline in this event and the token will be invalid anyways once Wings boots back. + Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); + + $instance->property('revoked', false); + } + }); } + $log->reset(); + return $this->fractal->item($subuser->refresh()) ->transformWith($this->getTransformer(SubuserTransformer::class)) ->toArray(); @@ -147,14 +168,23 @@ class SubuserController extends ClientApiController /** @var \Pterodactyl\Models\Subuser $subuser */ $subuser = $request->attributes->get('subuser'); - $this->repository->delete($subuser->id); + $log = Activity::event('server:subuser.delete') + ->subject($subuser->user) + ->property('email', $subuser->user->email) + ->property('revoked', true); - try { - $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); - } catch (DaemonConnectionException $exception) { - // Don't block this request if we can't connect to the Wings instance. - Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); - } + $log->transaction(function ($instance) use ($server, $subuser) { + $subuser->delete(); + + try { + $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); + } catch (DaemonConnectionException $exception) { + // Don't block this request if we can't connect to the Wings instance. + Log::warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); + + $instance->property('revoked', false); + } + }); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 47b560e48..44b4f5bf1 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -122,6 +122,11 @@ class Allocation extends Model return !is_null($this->ip_alias); } + public function toString(): string + { + return sprintf('%s:%s', $this->ip, $this->port); + } + /** * Gets information for the server associated with this allocation. * diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 5b335f9a5..5b38a4175 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,12 +4,8 @@ namespace Pterodactyl\Providers; use View; use Cache; +use Pterodactyl\Models; use Illuminate\Support\Str; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Models\Backup; -use Pterodactyl\Models\ApiKey; -use Pterodactyl\Models\UserSSHKey; use Illuminate\Support\Facades\URL; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Schema; @@ -41,11 +37,17 @@ class AppServiceProvider extends ServiceProvider } Relation::enforceMorphMap([ - 'api_key' => ApiKey::class, - 'backup' => Backup::class, - 'server' => Server::class, - 'ssh_key' => UserSSHKey::class, - 'user' => User::class, + 'allocation' => Models\Allocation::class, + 'api_key' => Models\ApiKey::class, + 'backup' => Models\Backup::class, + 'database' => Models\Database::class, + 'egg' => Models\Egg::class, + 'egg_variable' => Models\EggVariable::class, + 'schedule' => Models\Schedule::class, + 'server' => Models\Server::class, + 'ssh_key' => Models\UserSSHKey::class, + 'task' => Models\Task::class, + 'user' => Models\User::class, ]); } diff --git a/app/Services/Activity/ActivityLogService.php b/app/Services/Activity/ActivityLogService.php index 9726b657f..cb45dc33d 100644 --- a/app/Services/Activity/ActivityLogService.php +++ b/app/Services/Activity/ActivityLogService.php @@ -188,6 +188,15 @@ class ActivityLogService }); } + /** + * Resets the instance and clears out the log. + */ + public function reset(): void + { + $this->activity = null; + $this->subjects = []; + } + /** * Returns the current activity log instance. */