Update backup logic to use activity logs, not audit logs
This commit is contained in:
		
							parent
							
								
									cbecfff6da
								
							
						
					
					
						commit
						2fc5a734f9
					
				| @ -10,11 +10,10 @@ use Pterodactyl\Services\Activity\ActivityLogService; | |||||||
|  * @method static ActivityLogService anonymous() |  * @method static ActivityLogService anonymous() | ||||||
|  * @method static ActivityLogService event(string $action) |  * @method static ActivityLogService event(string $action) | ||||||
|  * @method static ActivityLogService description(?string $description) |  * @method static ActivityLogService description(?string $description) | ||||||
|  * @method static ActivityLogService subject(Model $subject) |  * @method static ActivityLogService subject(Model|Model[] $subject) | ||||||
|  * @method static ActivityLogService actor(Model $actor) |  * @method static ActivityLogService actor(Model $actor) | ||||||
|  * @method static ActivityLogService withProperties(\Illuminate\Support\Collection|array $properties) |  | ||||||
|  * @method static ActivityLogService withRequestMetadata() |  * @method static ActivityLogService withRequestMetadata() | ||||||
|  * @method static ActivityLogService property(string $key, mixed $value) |  * @method static ActivityLogService property(string|array $key, mixed $value = null) | ||||||
|  * @method static \Pterodactyl\Models\ActivityLog log(string $description = null) |  * @method static \Pterodactyl\Models\ActivityLog log(string $description = null) | ||||||
|  * @method static ActivityLogService clone() |  * @method static ActivityLogService clone() | ||||||
|  * @method static mixed transaction(\Closure $callback) |  * @method static mixed transaction(\Closure $callback) | ||||||
|  | |||||||
| @ -5,8 +5,8 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; | |||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Pterodactyl\Models\Backup; | use Pterodactyl\Models\Backup; | ||||||
| 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\Models\Permission; | use Pterodactyl\Models\Permission; | ||||||
| use Illuminate\Auth\Access\AuthorizationException; | use Illuminate\Auth\Access\AuthorizationException; | ||||||
| use Pterodactyl\Services\Backups\DeleteBackupService; | use Pterodactyl\Services\Backups\DeleteBackupService; | ||||||
| @ -77,25 +77,23 @@ class BackupController extends ClientApiController | |||||||
|      */ |      */ | ||||||
|     public function store(StoreBackupRequest $request, Server $server): array |     public function store(StoreBackupRequest $request, Server $server): array | ||||||
|     { |     { | ||||||
|         /** @var \Pterodactyl\Models\Backup $backup */ |         $action = $this->initiateBackupService | ||||||
|         $backup = $server->audit(AuditLog::SERVER__BACKUP_STARTED, function (AuditLog $model, Server $server) use ($request) { |             ->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? '')); | ||||||
|             $action = $this->initiateBackupService |  | ||||||
|                 ->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? '')); |  | ||||||
| 
 | 
 | ||||||
|             // Only set the lock status if the user even has permission to delete backups,
 |         // Only set the lock status if the user even has permission to delete backups,
 | ||||||
|             // otherwise ignore this status. This gets a little funky since it isn't clear
 |         // otherwise ignore this status. This gets a little funky since it isn't clear
 | ||||||
|             // how best to allow a user to create a backup that is locked without also preventing
 |         // how best to allow a user to create a backup that is locked without also preventing
 | ||||||
|             // them from just filling up a server with backups that can never be deleted?
 |         // them from just filling up a server with backups that can never be deleted?
 | ||||||
|             if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { |         if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { | ||||||
|                 $action->setIsLocked((bool) $request->input('is_locked')); |             $action->setIsLocked((bool) $request->input('is_locked')); | ||||||
|             } |         } | ||||||
| 
 | 
 | ||||||
|             $backup = $action->handle($server, $request->input('name')); |         $backup = $action->handle($server, $request->input('name')); | ||||||
| 
 | 
 | ||||||
|             $model->metadata = ['backup_uuid' => $backup->uuid]; |         Activity::event('server:backup.start') | ||||||
| 
 |             ->subject($backup) | ||||||
|             return $backup; |             ->property(['name' => $backup->name, 'locked' => (bool) $request->input('is_locked')]) | ||||||
|         }); |             ->log(); | ||||||
| 
 | 
 | ||||||
|         return $this->fractal->item($backup) |         return $this->fractal->item($backup) | ||||||
|             ->transformWith($this->getTransformer(BackupTransformer::class)) |             ->transformWith($this->getTransformer(BackupTransformer::class)) | ||||||
| @ -114,14 +112,11 @@ class BackupController extends ClientApiController | |||||||
|             throw new AuthorizationException(); |             throw new AuthorizationException(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $action = $backup->is_locked ? AuditLog::SERVER__BACKUP_UNLOCKED : AuditLog::SERVER__BACKUP_LOCKED; |         $action = $backup->is_locked ? 'server:backup.unlock' : 'server:backup.lock'; | ||||||
|         $server->audit($action, function (AuditLog $audit) use ($backup) { |  | ||||||
|             $audit->metadata = ['backup_uuid' => $backup->uuid]; |  | ||||||
| 
 | 
 | ||||||
|             $backup->update(['is_locked' => !$backup->is_locked]); |         $backup->update(['is_locked' => !$backup->is_locked]); | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         $backup->refresh(); |         Activity::event($action)->subject($backup)->property('name', $backup->name)->log(); | ||||||
| 
 | 
 | ||||||
|         return $this->fractal->item($backup) |         return $this->fractal->item($backup) | ||||||
|             ->transformWith($this->getTransformer(BackupTransformer::class)) |             ->transformWith($this->getTransformer(BackupTransformer::class)) | ||||||
| @ -156,11 +151,12 @@ class BackupController extends ClientApiController | |||||||
|             throw new AuthorizationException(); |             throw new AuthorizationException(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $server->audit(AuditLog::SERVER__BACKUP_DELETED, function (AuditLog $audit) use ($backup) { |         $this->deleteBackupService->handle($backup); | ||||||
|             $audit->metadata = ['backup_uuid' => $backup->uuid]; |  | ||||||
| 
 | 
 | ||||||
|             $this->deleteBackupService->handle($backup); |         Activity::event('server:backup.delete') | ||||||
|         }); |             ->subject($backup) | ||||||
|  |             ->property(['name' => $backup->name, 'failed' => !$backup->is_successful]) | ||||||
|  |             ->log(); | ||||||
| 
 | 
 | ||||||
|         return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); |         return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); | ||||||
|     } |     } | ||||||
| @ -184,9 +180,8 @@ class BackupController extends ClientApiController | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $url = $this->downloadLinkService->handle($backup, $request->user()); |         $url = $this->downloadLinkService->handle($backup, $request->user()); | ||||||
|         $server->audit(AuditLog::SERVER__BACKUP_DOWNLOADED, function (AuditLog $audit) use ($backup) { | 
 | ||||||
|             $audit->metadata = ['backup_uuid' => $backup->uuid]; |         Activity::event('server:backup.download')->subject($backup)->property('name', $backup->name)->log(); | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         return new JsonResponse([ |         return new JsonResponse([ | ||||||
|             'object' => 'signed_url', |             'object' => 'signed_url', | ||||||
| @ -221,9 +216,11 @@ class BackupController extends ClientApiController | |||||||
|             throw new BadRequestHttpException('This backup cannot be restored at this time: not completed or failed.'); |             throw new BadRequestHttpException('This backup cannot be restored at this time: not completed or failed.'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $server->audit(AuditLog::SERVER__BACKUP_RESTORE_STARTED, function (AuditLog $audit, Server $server) use ($backup, $request) { |         $log = Activity::event('server:backup.restore') | ||||||
|             $audit->metadata = ['backup_uuid' => $backup->uuid]; |             ->subject($backup) | ||||||
|  |             ->property(['name' => $backup->name, 'truncate' => $request->input('truncate')]); | ||||||
| 
 | 
 | ||||||
|  |         $log->transaction(function () use ($backup, $server, $request) { | ||||||
|             // If the backup is for an S3 file we need to generate a unique Download link for
 |             // If the backup is for an S3 file we need to generate a unique Download link for
 | ||||||
|             // it that will allow Wings to actually access the file.
 |             // it that will allow Wings to actually access the file.
 | ||||||
|             if ($backup->disk === Backup::ADAPTER_AWS_S3) { |             if ($backup->disk === Backup::ADAPTER_AWS_S3) { | ||||||
|  | |||||||
| @ -5,9 +5,8 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Backups; | |||||||
| use Carbon\CarbonImmutable; | use Carbon\CarbonImmutable; | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Pterodactyl\Models\Backup; | use Pterodactyl\Models\Backup; | ||||||
| use Pterodactyl\Models\Server; |  | ||||||
| use Pterodactyl\Models\AuditLog; |  | ||||||
| use Illuminate\Http\JsonResponse; | use Illuminate\Http\JsonResponse; | ||||||
|  | use Pterodactyl\Facades\Activity; | ||||||
| use League\Flysystem\AwsS3v3\AwsS3Adapter; | use League\Flysystem\AwsS3v3\AwsS3Adapter; | ||||||
| use Pterodactyl\Exceptions\DisplayException; | use Pterodactyl\Exceptions\DisplayException; | ||||||
| use Pterodactyl\Http\Controllers\Controller; | use Pterodactyl\Http\Controllers\Controller; | ||||||
| @ -46,15 +45,12 @@ class BackupStatusController extends Controller | |||||||
|             throw new BadRequestHttpException('Cannot update the status of a backup that is already marked as completed.'); |             throw new BadRequestHttpException('Cannot update the status of a backup that is already marked as completed.'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $action = $request->input('successful') |         $action = $request->boolean('successful') ? 'server:backup.complete' : 'server:backup.failed'; | ||||||
|             ? AuditLog::SERVER__BACKUP_COMPELTED |         $log = Activity::event($action)->subject($model, $model->server)->property('name', $model->name); | ||||||
|             : AuditLog::SERVER__BACKUP_FAILED; |  | ||||||
| 
 |  | ||||||
|         $model->server->audit($action, function (AuditLog $audit) use ($model, $request) { |  | ||||||
|             $audit->is_system = true; |  | ||||||
|             $audit->metadata = ['backup_uuid' => $model->uuid]; |  | ||||||
| 
 | 
 | ||||||
|  |         $log->transaction(function () use ($model, $request) { | ||||||
|             $successful = $request->boolean('successful'); |             $successful = $request->boolean('successful'); | ||||||
|  | 
 | ||||||
|             $model->fill([ |             $model->fill([ | ||||||
|                 'is_successful' => $successful, |                 'is_successful' => $successful, | ||||||
|                 // Change the lock state to unlocked if this was a failed backup so that it can be
 |                 // Change the lock state to unlocked if this was a failed backup so that it can be
 | ||||||
| @ -93,17 +89,13 @@ class BackupStatusController extends Controller | |||||||
|     { |     { | ||||||
|         /** @var \Pterodactyl\Models\Backup $model */ |         /** @var \Pterodactyl\Models\Backup $model */ | ||||||
|         $model = Backup::query()->where('uuid', $backup)->firstOrFail(); |         $model = Backup::query()->where('uuid', $backup)->firstOrFail(); | ||||||
|         $action = $request->get('successful') |  | ||||||
|             ? AuditLog::SERVER__BACKUP_RESTORE_COMPLETED |  | ||||||
|             : AuditLog::SERVER__BACKUP_RESTORE_FAILED; |  | ||||||
| 
 | 
 | ||||||
|         // Just create a new audit entry for this event and update the server state
 |         $model->server->update(['status' => null]); | ||||||
|         // so that power actions, file management, and backups can resume as normal.
 | 
 | ||||||
|         $model->server->audit($action, function (AuditLog $audit, Server $server) use ($backup) { |         Activity::event($request->boolean('successful') ? 'server:backup.restore-complete' : 'server.backup.restore-failed') | ||||||
|             $audit->is_system = true; |             ->subject($model, $model->server) | ||||||
|             $audit->metadata = ['backup_uuid' => $backup]; |             ->property('name', $model->name) | ||||||
|             $server->update(['status' => null]); |             ->log(); | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); |         return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -4,8 +4,10 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; | |||||||
| 
 | 
 | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Pterodactyl\Models\Server; | use Pterodactyl\Models\Server; | ||||||
|  | use Pterodactyl\Models\Backup; | ||||||
| use Pterodactyl\Models\AuditLog; | use Pterodactyl\Models\AuditLog; | ||||||
| use Illuminate\Http\JsonResponse; | use Illuminate\Http\JsonResponse; | ||||||
|  | use Pterodactyl\Facades\Activity; | ||||||
| use Illuminate\Database\Query\Builder; | use Illuminate\Database\Query\Builder; | ||||||
| use Illuminate\Database\Query\JoinClause; | use Illuminate\Database\Query\JoinClause; | ||||||
| use Pterodactyl\Http\Controllers\Controller; | use Pterodactyl\Http\Controllers\Controller; | ||||||
| @ -107,7 +109,6 @@ class ServerDetailsController extends Controller | |||||||
|         //
 |         //
 | ||||||
|         // For each of those servers we'll track a new audit log entry to mark them as
 |         // For each of those servers we'll track a new audit log entry to mark them as
 | ||||||
|         // failed and then update them all to be in a valid state.
 |         // failed and then update them all to be in a valid state.
 | ||||||
|         /** @var \Pterodactyl\Models\Server[] $servers */ |  | ||||||
|         $servers = Server::query() |         $servers = Server::query() | ||||||
|             ->select('servers.*') |             ->select('servers.*') | ||||||
|             ->selectRaw('JSON_UNQUOTE(JSON_EXTRACT(started.metadata, "$.backup_uuid")) as backup_uuid') |             ->selectRaw('JSON_UNQUOTE(JSON_EXTRACT(started.metadata, "$.backup_uuid")) as backup_uuid') | ||||||
| @ -130,14 +131,17 @@ class ServerDetailsController extends Controller | |||||||
|             ->where('servers.status', Server::STATUS_RESTORING_BACKUP) |             ->where('servers.status', Server::STATUS_RESTORING_BACKUP) | ||||||
|             ->get(); |             ->get(); | ||||||
| 
 | 
 | ||||||
|  |         $backups = Backup::query()->whereIn('uuid', $servers->pluck('backup_uuid'))->get(); | ||||||
|  | 
 | ||||||
|  |         /** @var \Pterodactyl\Models\Server $server */ | ||||||
|         foreach ($servers as $server) { |         foreach ($servers as $server) { | ||||||
|             // Just create a new audit entry for this event and update the server state
 |             $server->update(['status' => null]); | ||||||
|             // so that power actions, file management, and backups can resume as normal.
 | 
 | ||||||
|             $server->audit(AuditLog::SERVER__BACKUP_RESTORE_FAILED, function (AuditLog $audit, Server $server) { |             if ($backup = $backups->where('uuid', $server->getAttribute('backup_uuid'))->first()) { | ||||||
|                 $audit->is_system = true; |                 // Just create a new audit entry for this event and update the server state
 | ||||||
|                 $audit->metadata = ['backup_uuid' => $server->getAttribute('backup_uuid')]; |                 // so that power actions, file management, and backups can resume as normal.
 | ||||||
|                 $server->update(['status' => null]); |                 Activity::event('server:backup.restore-failed')->subject($server, $backup)->log(); | ||||||
|             }); |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Update any server marked as installing or restoring as being in a normal state
 |         // Update any server marked as installing or restoring as being in a normal state
 | ||||||
|  | |||||||
| @ -16,16 +16,13 @@ use Illuminate\Database\Eloquent\Model as IlluminateModel; | |||||||
|  * @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 | ||||||
|  * @property string|null $subject_type |  | ||||||
|  * @property int|null $subject_id |  | ||||||
|  * @property \Illuminate\Support\Collection $properties |  * @property \Illuminate\Support\Collection $properties | ||||||
|  * @property string $timestamp |  * @property string $timestamp | ||||||
|  * @property IlluminateModel|\Eloquent $actor |  * @property IlluminateModel|\Eloquent $actor | ||||||
|  * @property IlluminateModel|\Eloquent $subject |  * @property IlluminateModel|\Eloquent $subject | ||||||
|  * |  * | ||||||
|  * @method static Builder|ActivityLog forAction(string $action) |  * @method static Builder|ActivityLog forEvent(string $event) | ||||||
|  * @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor) |  * @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor) | ||||||
|  * @method static Builder|ActivityLog forSubject(\Illuminate\Database\Eloquent\Model $subject) |  | ||||||
|  * @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() | ||||||
| @ -37,8 +34,6 @@ use Illuminate\Database\Eloquent\Model as IlluminateModel; | |||||||
|  * @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) | ||||||
|  * @method static Builder|ActivityLog whereSubjectId($value) |  | ||||||
|  * @method static Builder|ActivityLog whereSubjectType($value) |  | ||||||
|  * @method static Builder|ActivityLog whereTimestamp($value) |  * @method static Builder|ActivityLog whereTimestamp($value) | ||||||
|  * @mixin \Eloquent |  * @mixin \Eloquent | ||||||
|  */ |  */ | ||||||
| @ -68,14 +63,9 @@ class ActivityLog extends Model | |||||||
|         return $this->morphTo(); |         return $this->morphTo(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function subject(): MorphTo |     public function scopeForEvent(Builder $builder, string $action): Builder | ||||||
|     { |     { | ||||||
|         return $this->morphTo(); |         return $builder->where('event', $action); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function scopeForAction(Builder $builder, string $action): Builder |  | ||||||
|     { |  | ||||||
|         return $builder->where('action', $action); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -85,12 +75,4 @@ class ActivityLog extends Model | |||||||
|     { |     { | ||||||
|         return $builder->whereMorphedTo('actor', $actor); |         return $builder->whereMorphedTo('actor', $actor); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Scopes a query to only return results where the subject is the given model. |  | ||||||
|      */ |  | ||||||
|     public function scopeForSubject(Builder $builder, IlluminateModel $subject): Builder |  | ||||||
|     { |  | ||||||
|         return $builder->whereMorphedTo('subject', $subject); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								app/Models/ActivityLogSubject.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/Models/ActivityLogSubject.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace Pterodactyl\Models; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Eloquent\Relations\Pivot; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * \Pterodactyl\Models\ActivityLogSubject. | ||||||
|  |  * | ||||||
|  |  * @property int $id | ||||||
|  |  * @property int $activity_log_id | ||||||
|  |  * @property int $subject_id | ||||||
|  |  * @property string $subject_type | ||||||
|  |  * @property \Pterodactyl\Models\ActivityLog|null $activityLog | ||||||
|  |  * @property \Illuminate\Database\Eloquent\Model|\Eloquent $subject | ||||||
|  |  * | ||||||
|  |  * @method static \Illuminate\Database\Eloquent\Builder|ActivityLogSubject newModelQuery() | ||||||
|  |  * @method static \Illuminate\Database\Eloquent\Builder|ActivityLogSubject newQuery() | ||||||
|  |  * @method static \Illuminate\Database\Eloquent\Builder|ActivityLogSubject query() | ||||||
|  |  * @mixin \Eloquent | ||||||
|  |  */ | ||||||
|  | class ActivityLogSubject extends Pivot | ||||||
|  | { | ||||||
|  |     public $incrementing = true; | ||||||
|  |     public $timestamps = false; | ||||||
|  | 
 | ||||||
|  |     protected $table = 'activity_log_subjects'; | ||||||
|  | 
 | ||||||
|  |     protected $guarded = ['id']; | ||||||
|  | 
 | ||||||
|  |     public function activityLog() | ||||||
|  |     { | ||||||
|  |         return $this->belongsTo(ActivityLog::class); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function subject() | ||||||
|  |     { | ||||||
|  |         return $this->morphTo(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -2,10 +2,10 @@ | |||||||
| 
 | 
 | ||||||
| namespace Pterodactyl\Models; | namespace Pterodactyl\Models; | ||||||
| 
 | 
 | ||||||
| use Closure; |  | ||||||
| use Illuminate\Notifications\Notifiable; | use Illuminate\Notifications\Notifiable; | ||||||
| use Illuminate\Database\Query\JoinClause; | use Illuminate\Database\Query\JoinClause; | ||||||
| use Znck\Eloquent\Traits\BelongsToThrough; | use Znck\Eloquent\Traits\BelongsToThrough; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\MorphToMany; | ||||||
| use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; | use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -41,8 +41,6 @@ use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException; | |||||||
|  * @property \Pterodactyl\Models\Allocation|null $allocation |  * @property \Pterodactyl\Models\Allocation|null $allocation | ||||||
|  * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Allocation[] $allocations |  * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Allocation[] $allocations | ||||||
|  * @property int|null $allocations_count |  * @property int|null $allocations_count | ||||||
|  * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\AuditLog[] $audits |  | ||||||
|  * @property int|null $audits_count |  | ||||||
|  * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Backup[] $backups |  * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Backup[] $backups | ||||||
|  * @property int|null $backups_count |  * @property int|null $backups_count | ||||||
|  * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Database[] $databases |  * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Database[] $databases | ||||||
| @ -373,48 +371,11 @@ class Server extends Model | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns a fresh AuditLog model for the server. This model is not saved to the |      * Returns all of the activity log entries where the server is the subject. | ||||||
|      * database when created, so it is up to the caller to correctly store it as needed. |  | ||||||
|      * |  | ||||||
|      * @return \Pterodactyl\Models\AuditLog |  | ||||||
|      */ |      */ | ||||||
|     public function newAuditEvent(string $action, array $metadata = []): AuditLog |     public function activity(): MorphToMany | ||||||
|     { |     { | ||||||
|         return AuditLog::instance($action, $metadata)->fill([ |         return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); | ||||||
|             'server_id' => $this->id, |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Stores a new audit event for a server by using a transaction. If the transaction |  | ||||||
|      * fails for any reason everything executed within will be rolled back. The callback |  | ||||||
|      * passed in will receive the AuditLog model before it is saved and the second argument |  | ||||||
|      * will be the current server instance. The callback should modify the audit entry as |  | ||||||
|      * needed before finishing, any changes will be persisted. |  | ||||||
|      * |  | ||||||
|      * The response from the callback is returned to the caller. |  | ||||||
|      * |  | ||||||
|      * @return mixed |  | ||||||
|      * |  | ||||||
|      * @throws \Throwable |  | ||||||
|      */ |  | ||||||
|     public function audit(string $action, Closure $callback) |  | ||||||
|     { |  | ||||||
|         return $this->getConnection()->transaction(function () use ($action, $callback) { |  | ||||||
|             $model = $this->newAuditEvent($action); |  | ||||||
|             $response = $callback($model, $this); |  | ||||||
|             $model->save(); |  | ||||||
| 
 |  | ||||||
|             return $response; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * @return \Illuminate\Database\Eloquent\Relations\HasMany |  | ||||||
|      */ |  | ||||||
|     public function audits() |  | ||||||
|     { |  | ||||||
|         return $this->hasMany(AuditLog::class); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ use Illuminate\Auth\Passwords\CanResetPassword; | |||||||
| use Pterodactyl\Traits\Helpers\AvailableLanguages; | use Pterodactyl\Traits\Helpers\AvailableLanguages; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; | use Illuminate\Database\Eloquent\Relations\HasMany; | ||||||
| use Illuminate\Foundation\Auth\Access\Authorizable; | use Illuminate\Foundation\Auth\Access\Authorizable; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\MorphToMany; | ||||||
| use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; | use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; | ||||||
| use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; | use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; | ||||||
| use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; | use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; | ||||||
| @ -273,6 +274,15 @@ class User extends Model implements | |||||||
|         return $this->hasMany(UserSSHKey::class); |         return $this->hasMany(UserSSHKey::class); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns all of the activity logs where this user is the subject — not to | ||||||
|  |      * be confused by activity logs where this user is the _actor_. | ||||||
|  |      */ | ||||||
|  |     public function activity(): MorphToMany | ||||||
|  |     { | ||||||
|  |         return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns all of the servers that a user can access by way of being the owner of the |      * Returns all of the servers that a user can access by way of being the owner of the | ||||||
|      * server, or because they are assigned as a subuser for that server. |      * server, or because they are assigned as a subuser for that server. | ||||||
|  | |||||||
| @ -5,11 +5,15 @@ namespace Pterodactyl\Providers; | |||||||
| use View; | use View; | ||||||
| use Cache; | use Cache; | ||||||
| use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||||
|  | use Pterodactyl\Models\User; | ||||||
|  | use Pterodactyl\Models\Server; | ||||||
|  | use Pterodactyl\Models\Backup; | ||||||
| use Illuminate\Support\Facades\URL; | use Illuminate\Support\Facades\URL; | ||||||
| use Illuminate\Pagination\Paginator; | use Illuminate\Pagination\Paginator; | ||||||
| use Illuminate\Support\Facades\Schema; | use Illuminate\Support\Facades\Schema; | ||||||
| use Illuminate\Support\ServiceProvider; | use Illuminate\Support\ServiceProvider; | ||||||
| use Pterodactyl\Extensions\Themes\Theme; | use Pterodactyl\Extensions\Themes\Theme; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\Relation; | ||||||
| 
 | 
 | ||||||
| class AppServiceProvider extends ServiceProvider | class AppServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
| @ -33,6 +37,12 @@ class AppServiceProvider extends ServiceProvider | |||||||
|         if (Str::startsWith(config('app.url') ?? '', 'https://')) { |         if (Str::startsWith(config('app.url') ?? '', 'https://')) { | ||||||
|             URL::forceScheme('https'); |             URL::forceScheme('https'); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         Relation::enforceMorphMap([ | ||||||
|  |             'backup' => Backup::class, | ||||||
|  |             'server' => Server::class, | ||||||
|  |             'user' => User::class, | ||||||
|  |         ]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -2,20 +2,28 @@ | |||||||
| 
 | 
 | ||||||
| namespace Pterodactyl\Services\Activity; | namespace Pterodactyl\Services\Activity; | ||||||
| 
 | 
 | ||||||
|  | use Illuminate\Support\Arr; | ||||||
|  | use Webmozart\Assert\Assert; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Pterodactyl\Models\ActivityLog; | use Pterodactyl\Models\ActivityLog; | ||||||
| use Illuminate\Contracts\Auth\Factory; | use Illuminate\Contracts\Auth\Factory; | ||||||
| use Illuminate\Database\Eloquent\Model; | use Illuminate\Database\Eloquent\Model; | ||||||
| use Illuminate\Support\Facades\Request; | use Illuminate\Support\Facades\Request; | ||||||
|  | use Pterodactyl\Models\ActivityLogSubject; | ||||||
| use Illuminate\Database\ConnectionInterface; | use Illuminate\Database\ConnectionInterface; | ||||||
| 
 | 
 | ||||||
| class ActivityLogService | class ActivityLogService | ||||||
| { | { | ||||||
|     protected ?ActivityLog $activity = null; |     protected ?ActivityLog $activity = null; | ||||||
| 
 | 
 | ||||||
|  |     protected array $subjects = []; | ||||||
|  | 
 | ||||||
|     protected Factory $manager; |     protected Factory $manager; | ||||||
|  | 
 | ||||||
|     protected ConnectionInterface $connection; |     protected ConnectionInterface $connection; | ||||||
|  | 
 | ||||||
|     protected AcitvityLogBatchService $batch; |     protected AcitvityLogBatchService $batch; | ||||||
|  | 
 | ||||||
|     protected ActivityLogTargetableService $targetable; |     protected ActivityLogTargetableService $targetable; | ||||||
| 
 | 
 | ||||||
|     public function __construct( |     public function __construct( | ||||||
| @ -65,10 +73,22 @@ class ActivityLogService | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Sets the subject model instance. |      * Sets the subject model instance. | ||||||
|  |      * | ||||||
|  |      * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Model[] $subjects | ||||||
|      */ |      */ | ||||||
|     public function subject(Model $subject): self |     public function subject(...$subjects): self | ||||||
|     { |     { | ||||||
|         $this->getActivity()->subject()->associate($subject); |         foreach (Arr::wrap($subjects) as $subject) { | ||||||
|  |             foreach ($this->subjects as $entry) { | ||||||
|  |                 // If this subject is already tracked in our array of subjects just skip over
 | ||||||
|  |                 // it and move on to the next one in the list.
 | ||||||
|  |                 if ($entry->is($subject)) { | ||||||
|  |                     continue 2; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $this->subjects[] = $subject; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return $this; |         return $this; | ||||||
|     } |     } | ||||||
| @ -83,26 +103,18 @@ class ActivityLogService | |||||||
|         return $this; |         return $this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Sets the custom properties for the activity log instance. |  | ||||||
|      * |  | ||||||
|      * @param \Illuminate\Support\Collection|array $properties |  | ||||||
|      */ |  | ||||||
|     public function withProperties($properties): self |  | ||||||
|     { |  | ||||||
|         $this->getActivity()->properties = Collection::make($properties); |  | ||||||
| 
 |  | ||||||
|         return $this; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Sets a custom property on the activty log instance. |      * Sets a custom property on the activty log instance. | ||||||
|      * |      * | ||||||
|  |      * @param string|array $key | ||||||
|      * @param mixed $value |      * @param mixed $value | ||||||
|      */ |      */ | ||||||
|     public function property(string $key, $value): self |     public function property($key, $value = null): self | ||||||
|     { |     { | ||||||
|         $this->getActivity()->properties = $this->getActivity()->properties->put($key, $value); |         $properties = $this->getActivity()->properties; | ||||||
|  |         $this->activity->properties = is_array($key) | ||||||
|  |             ? $properties->merge($key) | ||||||
|  |             : $properties->put($key, $value); | ||||||
| 
 | 
 | ||||||
|         return $this; |         return $this; | ||||||
|     } |     } | ||||||
| @ -112,10 +124,10 @@ class ActivityLogService | |||||||
|      */ |      */ | ||||||
|     public function withRequestMetadata(): self |     public function withRequestMetadata(): self | ||||||
|     { |     { | ||||||
|         $this->property('ip', Request::getClientIp()); |         return $this->property([ | ||||||
|         $this->property('useragent', Request::userAgent()); |             'ip' => Request::getClientIp(), | ||||||
| 
 |             'useragent' => Request::userAgent(), | ||||||
|         return $this; |         ]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -130,11 +142,7 @@ class ActivityLogService | |||||||
|             $activity->description = $description; |             $activity->description = $description; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $activity->save(); |         return $this->save(); | ||||||
| 
 |  | ||||||
|         $this->activity = null; |  | ||||||
| 
 |  | ||||||
|         return $activity; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -155,17 +163,12 @@ class ActivityLogService | |||||||
|      * |      * | ||||||
|      * @throws \Throwable |      * @throws \Throwable | ||||||
|      */ |      */ | ||||||
|     public function transaction(\Closure $callback, string $description = null) |     public function transaction(\Closure $callback) | ||||||
|     { |     { | ||||||
|         if (!is_null($description)) { |  | ||||||
|             $this->description($description); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return $this->connection->transaction(function () use ($callback) { |         return $this->connection->transaction(function () use ($callback) { | ||||||
|             $response = $callback($activity = $this->getActivity()); |             $response = $callback($activity = $this->getActivity()); | ||||||
| 
 | 
 | ||||||
|             $activity->save(); |             $this->save($activity); | ||||||
|             $this->activity = null; |  | ||||||
| 
 | 
 | ||||||
|             return $response; |             return $response; | ||||||
|         }); |         }); | ||||||
| @ -200,4 +203,38 @@ class ActivityLogService | |||||||
| 
 | 
 | ||||||
|         return $this->activity; |         return $this->activity; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Saves the activity log instance and attaches all of the subject models. | ||||||
|  |      * | ||||||
|  |      * @throws \Throwable | ||||||
|  |      */ | ||||||
|  |     protected function save(ActivityLog $activity = null): ActivityLog | ||||||
|  |     { | ||||||
|  |         $activity = $activity ?? $this->activity; | ||||||
|  | 
 | ||||||
|  |         Assert::notNull($activity); | ||||||
|  | 
 | ||||||
|  |         $response = $this->connection->transaction(function () use ($activity) { | ||||||
|  |             $activity->save(); | ||||||
|  | 
 | ||||||
|  |             $subjects = Collection::make($this->subjects) | ||||||
|  |                 ->map(fn (Model $subject) => [ | ||||||
|  |                     'activity_log_id' => $this->activity->id, | ||||||
|  |                     'subject_id' => $subject->getKey(), | ||||||
|  |                     'subject_type' => $subject->getMorphClass(), | ||||||
|  |                 ]) | ||||||
|  |                 ->values() | ||||||
|  |                 ->toArray(); | ||||||
|  | 
 | ||||||
|  |             ActivityLogSubject::insert($subjects); | ||||||
|  | 
 | ||||||
|  |             return $activity; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         $this->activity = null; | ||||||
|  |         $this->subjects = []; | ||||||
|  | 
 | ||||||
|  |         return $response; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,7 +20,6 @@ class CreateActivityLogsTable extends Migration | |||||||
|             $table->string('ip'); |             $table->string('ip'); | ||||||
|             $table->text('description')->nullable(); |             $table->text('description')->nullable(); | ||||||
|             $table->nullableNumericMorphs('actor'); |             $table->nullableNumericMorphs('actor'); | ||||||
|             $table->nullableNumericMorphs('subject'); |  | ||||||
|             $table->json('properties'); |             $table->json('properties'); | ||||||
|             $table->timestamp('timestamp')->useCurrent()->onUpdate(null); |             $table->timestamp('timestamp')->useCurrent()->onUpdate(null); | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -0,0 +1,32 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | 
 | ||||||
|  | class CreateActivityLogActorsTable extends Migration | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Run the migrations. | ||||||
|  |      * | ||||||
|  |      * @return void | ||||||
|  |      */ | ||||||
|  |     public function up() | ||||||
|  |     { | ||||||
|  |         Schema::create('activity_log_subjects', function (Blueprint $table) { | ||||||
|  |             $table->id(); | ||||||
|  |             $table->foreignId('activity_log_id')->references('id')->on('activity_logs'); | ||||||
|  |             $table->numericMorphs('subject'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Reverse the migrations. | ||||||
|  |      * | ||||||
|  |      * @return void | ||||||
|  |      */ | ||||||
|  |     public function down() | ||||||
|  |     { | ||||||
|  |         Schema::dropIfExists('activity_log_subject'); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DaneEveritt
						DaneEveritt