Skip to content

Commit 2fc5a73

Browse files
committed
Update backup logic to use activity logs, not audit logs
1 parent cbecfff commit 2fc5a73

File tree

12 files changed

+224
-161
lines changed

12 files changed

+224
-161
lines changed

app/Facades/Activity.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
* @method static ActivityLogService anonymous()
1111
* @method static ActivityLogService event(string $action)
1212
* @method static ActivityLogService description(?string $description)
13-
* @method static ActivityLogService subject(Model $subject)
13+
* @method static ActivityLogService subject(Model|Model[] $subject)
1414
* @method static ActivityLogService actor(Model $actor)
15-
* @method static ActivityLogService withProperties(\Illuminate\Support\Collection|array $properties)
1615
* @method static ActivityLogService withRequestMetadata()
17-
* @method static ActivityLogService property(string $key, mixed $value)
16+
* @method static ActivityLogService property(string|array $key, mixed $value = null)
1817
* @method static \Pterodactyl\Models\ActivityLog log(string $description = null)
1918
* @method static ActivityLogService clone()
2019
* @method static mixed transaction(\Closure $callback)

app/Http/Controllers/Api/Client/Servers/BackupController.php

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
use Illuminate\Http\Request;
66
use Pterodactyl\Models\Backup;
77
use Pterodactyl\Models\Server;
8-
use Pterodactyl\Models\AuditLog;
98
use Illuminate\Http\JsonResponse;
9+
use Pterodactyl\Facades\Activity;
1010
use Pterodactyl\Models\Permission;
1111
use Illuminate\Auth\Access\AuthorizationException;
1212
use Pterodactyl\Services\Backups\DeleteBackupService;
@@ -77,25 +77,23 @@ public function index(Request $request, Server $server): array
7777
*/
7878
public function store(StoreBackupRequest $request, Server $server): array
7979
{
80-
/** @var \Pterodactyl\Models\Backup $backup */
81-
$backup = $server->audit(AuditLog::SERVER__BACKUP_STARTED, function (AuditLog $model, Server $server) use ($request) {
82-
$action = $this->initiateBackupService
83-
->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? ''));
84-
85-
// Only set the lock status if the user even has permission to delete backups,
86-
// otherwise ignore this status. This gets a little funky since it isn't clear
87-
// how best to allow a user to create a backup that is locked without also preventing
88-
// them from just filling up a server with backups that can never be deleted?
89-
if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) {
90-
$action->setIsLocked((bool) $request->input('is_locked'));
91-
}
92-
93-
$backup = $action->handle($server, $request->input('name'));
80+
$action = $this->initiateBackupService
81+
->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? ''));
82+
83+
// Only set the lock status if the user even has permission to delete backups,
84+
// otherwise ignore this status. This gets a little funky since it isn't clear
85+
// how best to allow a user to create a backup that is locked without also preventing
86+
// them from just filling up a server with backups that can never be deleted?
87+
if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) {
88+
$action->setIsLocked((bool) $request->input('is_locked'));
89+
}
9490

95-
$model->metadata = ['backup_uuid' => $backup->uuid];
91+
$backup = $action->handle($server, $request->input('name'));
9692

97-
return $backup;
98-
});
93+
Activity::event('server:backup.start')
94+
->subject($backup)
95+
->property(['name' => $backup->name, 'locked' => (bool) $request->input('is_locked')])
96+
->log();
9997

10098
return $this->fractal->item($backup)
10199
->transformWith($this->getTransformer(BackupTransformer::class))
@@ -114,14 +112,11 @@ public function toggleLock(Request $request, Server $server, Backup $backup): ar
114112
throw new AuthorizationException();
115113
}
116114

117-
$action = $backup->is_locked ? AuditLog::SERVER__BACKUP_UNLOCKED : AuditLog::SERVER__BACKUP_LOCKED;
118-
$server->audit($action, function (AuditLog $audit) use ($backup) {
119-
$audit->metadata = ['backup_uuid' => $backup->uuid];
115+
$action = $backup->is_locked ? 'server:backup.unlock' : 'server:backup.lock';
120116

121-
$backup->update(['is_locked' => !$backup->is_locked]);
122-
});
117+
$backup->update(['is_locked' => !$backup->is_locked]);
123118

124-
$backup->refresh();
119+
Activity::event($action)->subject($backup)->property('name', $backup->name)->log();
125120

126121
return $this->fractal->item($backup)
127122
->transformWith($this->getTransformer(BackupTransformer::class))
@@ -156,11 +151,12 @@ public function delete(Request $request, Server $server, Backup $backup): JsonRe
156151
throw new AuthorizationException();
157152
}
158153

159-
$server->audit(AuditLog::SERVER__BACKUP_DELETED, function (AuditLog $audit) use ($backup) {
160-
$audit->metadata = ['backup_uuid' => $backup->uuid];
154+
$this->deleteBackupService->handle($backup);
161155

162-
$this->deleteBackupService->handle($backup);
163-
});
156+
Activity::event('server:backup.delete')
157+
->subject($backup)
158+
->property(['name' => $backup->name, 'failed' => !$backup->is_successful])
159+
->log();
164160

165161
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
166162
}
@@ -184,9 +180,8 @@ public function download(Request $request, Server $server, Backup $backup): Json
184180
}
185181

186182
$url = $this->downloadLinkService->handle($backup, $request->user());
187-
$server->audit(AuditLog::SERVER__BACKUP_DOWNLOADED, function (AuditLog $audit) use ($backup) {
188-
$audit->metadata = ['backup_uuid' => $backup->uuid];
189-
});
183+
184+
Activity::event('server:backup.download')->subject($backup)->property('name', $backup->name)->log();
190185

191186
return new JsonResponse([
192187
'object' => 'signed_url',
@@ -221,9 +216,11 @@ public function restore(Request $request, Server $server, Backup $backup): JsonR
221216
throw new BadRequestHttpException('This backup cannot be restored at this time: not completed or failed.');
222217
}
223218

224-
$server->audit(AuditLog::SERVER__BACKUP_RESTORE_STARTED, function (AuditLog $audit, Server $server) use ($backup, $request) {
225-
$audit->metadata = ['backup_uuid' => $backup->uuid];
219+
$log = Activity::event('server:backup.restore')
220+
->subject($backup)
221+
->property(['name' => $backup->name, 'truncate' => $request->input('truncate')]);
226222

223+
$log->transaction(function () use ($backup, $server, $request) {
227224
// If the backup is for an S3 file we need to generate a unique Download link for
228225
// it that will allow Wings to actually access the file.
229226
if ($backup->disk === Backup::ADAPTER_AWS_S3) {

app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
use Carbon\CarbonImmutable;
66
use Illuminate\Http\Request;
77
use Pterodactyl\Models\Backup;
8-
use Pterodactyl\Models\Server;
9-
use Pterodactyl\Models\AuditLog;
108
use Illuminate\Http\JsonResponse;
9+
use Pterodactyl\Facades\Activity;
1110
use League\Flysystem\AwsS3v3\AwsS3Adapter;
1211
use Pterodactyl\Exceptions\DisplayException;
1312
use Pterodactyl\Http\Controllers\Controller;
@@ -46,15 +45,12 @@ public function index(ReportBackupCompleteRequest $request, string $backup)
4645
throw new BadRequestHttpException('Cannot update the status of a backup that is already marked as completed.');
4746
}
4847

49-
$action = $request->input('successful')
50-
? AuditLog::SERVER__BACKUP_COMPELTED
51-
: AuditLog::SERVER__BACKUP_FAILED;
52-
53-
$model->server->audit($action, function (AuditLog $audit) use ($model, $request) {
54-
$audit->is_system = true;
55-
$audit->metadata = ['backup_uuid' => $model->uuid];
48+
$action = $request->boolean('successful') ? 'server:backup.complete' : 'server:backup.failed';
49+
$log = Activity::event($action)->subject($model, $model->server)->property('name', $model->name);
5650

51+
$log->transaction(function () use ($model, $request) {
5752
$successful = $request->boolean('successful');
53+
5854
$model->fill([
5955
'is_successful' => $successful,
6056
// Change the lock state to unlocked if this was a failed backup so that it can be
@@ -93,17 +89,13 @@ public function restore(Request $request, string $backup)
9389
{
9490
/** @var \Pterodactyl\Models\Backup $model */
9591
$model = Backup::query()->where('uuid', $backup)->firstOrFail();
96-
$action = $request->get('successful')
97-
? AuditLog::SERVER__BACKUP_RESTORE_COMPLETED
98-
: AuditLog::SERVER__BACKUP_RESTORE_FAILED;
99-
100-
// Just create a new audit entry for this event and update the server state
101-
// so that power actions, file management, and backups can resume as normal.
102-
$model->server->audit($action, function (AuditLog $audit, Server $server) use ($backup) {
103-
$audit->is_system = true;
104-
$audit->metadata = ['backup_uuid' => $backup];
105-
$server->update(['status' => null]);
106-
});
92+
93+
$model->server->update(['status' => null]);
94+
95+
Activity::event($request->boolean('successful') ? 'server:backup.restore-complete' : 'server.backup.restore-failed')
96+
->subject($model, $model->server)
97+
->property('name', $model->name)
98+
->log();
10799

108100
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
109101
}

app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
use Illuminate\Http\Request;
66
use Pterodactyl\Models\Server;
7+
use Pterodactyl\Models\Backup;
78
use Pterodactyl\Models\AuditLog;
89
use Illuminate\Http\JsonResponse;
10+
use Pterodactyl\Facades\Activity;
911
use Illuminate\Database\Query\Builder;
1012
use Illuminate\Database\Query\JoinClause;
1113
use Pterodactyl\Http\Controllers\Controller;
@@ -107,7 +109,6 @@ public function resetState(Request $request)
107109
//
108110
// For each of those servers we'll track a new audit log entry to mark them as
109111
// failed and then update them all to be in a valid state.
110-
/** @var \Pterodactyl\Models\Server[] $servers */
111112
$servers = Server::query()
112113
->select('servers.*')
113114
->selectRaw('JSON_UNQUOTE(JSON_EXTRACT(started.metadata, "$.backup_uuid")) as backup_uuid')
@@ -130,14 +131,17 @@ public function resetState(Request $request)
130131
->where('servers.status', Server::STATUS_RESTORING_BACKUP)
131132
->get();
132133

134+
$backups = Backup::query()->whereIn('uuid', $servers->pluck('backup_uuid'))->get();
135+
136+
/** @var \Pterodactyl\Models\Server $server */
133137
foreach ($servers as $server) {
134-
// Just create a new audit entry for this event and update the server state
135-
// so that power actions, file management, and backups can resume as normal.
136-
$server->audit(AuditLog::SERVER__BACKUP_RESTORE_FAILED, function (AuditLog $audit, Server $server) {
137-
$audit->is_system = true;
138-
$audit->metadata = ['backup_uuid' => $server->getAttribute('backup_uuid')];
139-
$server->update(['status' => null]);
140-
});
138+
$server->update(['status' => null]);
139+
140+
if ($backup = $backups->where('uuid', $server->getAttribute('backup_uuid'))->first()) {
141+
// Just create a new audit entry for this event and update the server state
142+
// so that power actions, file management, and backups can resume as normal.
143+
Activity::event('server:backup.restore-failed')->subject($server, $backup)->log();
144+
}
141145
}
142146

143147
// Update any server marked as installing or restoring as being in a normal state

app/Models/ActivityLog.php

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,13 @@
1616
* @property string|null $description
1717
* @property string|null $actor_type
1818
* @property int|null $actor_id
19-
* @property string|null $subject_type
20-
* @property int|null $subject_id
2119
* @property \Illuminate\Support\Collection $properties
2220
* @property string $timestamp
2321
* @property IlluminateModel|\Eloquent $actor
2422
* @property IlluminateModel|\Eloquent $subject
2523
*
26-
* @method static Builder|ActivityLog forAction(string $action)
24+
* @method static Builder|ActivityLog forEvent(string $event)
2725
* @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor)
28-
* @method static Builder|ActivityLog forSubject(\Illuminate\Database\Eloquent\Model $subject)
2926
* @method static Builder|ActivityLog newModelQuery()
3027
* @method static Builder|ActivityLog newQuery()
3128
* @method static Builder|ActivityLog query()
@@ -37,8 +34,6 @@
3734
* @method static Builder|ActivityLog whereId($value)
3835
* @method static Builder|ActivityLog whereIp($value)
3936
* @method static Builder|ActivityLog whereProperties($value)
40-
* @method static Builder|ActivityLog whereSubjectId($value)
41-
* @method static Builder|ActivityLog whereSubjectType($value)
4237
* @method static Builder|ActivityLog whereTimestamp($value)
4338
* @mixin \Eloquent
4439
*/
@@ -68,14 +63,9 @@ public function actor(): MorphTo
6863
return $this->morphTo();
6964
}
7065

71-
public function subject(): MorphTo
66+
public function scopeForEvent(Builder $builder, string $action): Builder
7267
{
73-
return $this->morphTo();
74-
}
75-
76-
public function scopeForAction(Builder $builder, string $action): Builder
77-
{
78-
return $builder->where('action', $action);
68+
return $builder->where('event', $action);
7969
}
8070

8171
/**
@@ -85,12 +75,4 @@ public function scopeForActor(Builder $builder, IlluminateModel $actor): Builder
8575
{
8676
return $builder->whereMorphedTo('actor', $actor);
8777
}
88-
89-
/**
90-
* Scopes a query to only return results where the subject is the given model.
91-
*/
92-
public function scopeForSubject(Builder $builder, IlluminateModel $subject): Builder
93-
{
94-
return $builder->whereMorphedTo('subject', $subject);
95-
}
9678
}

app/Models/ActivityLogSubject.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Pterodactyl\Models;
4+
5+
use Illuminate\Database\Eloquent\Relations\Pivot;
6+
7+
/**
8+
* \Pterodactyl\Models\ActivityLogSubject.
9+
*
10+
* @property int $id
11+
* @property int $activity_log_id
12+
* @property int $subject_id
13+
* @property string $subject_type
14+
* @property \Pterodactyl\Models\ActivityLog|null $activityLog
15+
* @property \Illuminate\Database\Eloquent\Model|\Eloquent $subject
16+
*
17+
* @method static \Illuminate\Database\Eloquent\Builder|ActivityLogSubject newModelQuery()
18+
* @method static \Illuminate\Database\Eloquent\Builder|ActivityLogSubject newQuery()
19+
* @method static \Illuminate\Database\Eloquent\Builder|ActivityLogSubject query()
20+
* @mixin \Eloquent
21+
*/
22+
class ActivityLogSubject extends Pivot
23+
{
24+
public $incrementing = true;
25+
public $timestamps = false;
26+
27+
protected $table = 'activity_log_subjects';
28+
29+
protected $guarded = ['id'];
30+
31+
public function activityLog()
32+
{
33+
return $this->belongsTo(ActivityLog::class);
34+
}
35+
36+
public function subject()
37+
{
38+
return $this->morphTo();
39+
}
40+
}

app/Models/Server.php

Lines changed: 4 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
namespace Pterodactyl\Models;
44

5-
use Closure;
65
use Illuminate\Notifications\Notifiable;
76
use Illuminate\Database\Query\JoinClause;
87
use Znck\Eloquent\Traits\BelongsToThrough;
8+
use Illuminate\Database\Eloquent\Relations\MorphToMany;
99
use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException;
1010

1111
/**
@@ -41,8 +41,6 @@
4141
* @property \Pterodactyl\Models\Allocation|null $allocation
4242
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Allocation[] $allocations
4343
* @property int|null $allocations_count
44-
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\AuditLog[] $audits
45-
* @property int|null $audits_count
4644
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Backup[] $backups
4745
* @property int|null $backups_count
4846
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Database[] $databases
@@ -373,48 +371,11 @@ public function mounts()
373371
}
374372

375373
/**
376-
* Returns a fresh AuditLog model for the server. This model is not saved to the
377-
* database when created, so it is up to the caller to correctly store it as needed.
378-
*
379-
* @return \Pterodactyl\Models\AuditLog
380-
*/
381-
public function newAuditEvent(string $action, array $metadata = []): AuditLog
382-
{
383-
return AuditLog::instance($action, $metadata)->fill([
384-
'server_id' => $this->id,
385-
]);
386-
}
387-
388-
/**
389-
* Stores a new audit event for a server by using a transaction. If the transaction
390-
* fails for any reason everything executed within will be rolled back. The callback
391-
* passed in will receive the AuditLog model before it is saved and the second argument
392-
* will be the current server instance. The callback should modify the audit entry as
393-
* needed before finishing, any changes will be persisted.
394-
*
395-
* The response from the callback is returned to the caller.
396-
*
397-
* @return mixed
398-
*
399-
* @throws \Throwable
400-
*/
401-
public function audit(string $action, Closure $callback)
402-
{
403-
return $this->getConnection()->transaction(function () use ($action, $callback) {
404-
$model = $this->newAuditEvent($action);
405-
$response = $callback($model, $this);
406-
$model->save();
407-
408-
return $response;
409-
});
410-
}
411-
412-
/**
413-
* @return \Illuminate\Database\Eloquent\Relations\HasMany
374+
* Returns all of the activity log entries where the server is the subject.
414375
*/
415-
public function audits()
376+
public function activity(): MorphToMany
416377
{
417-
return $this->hasMany(AuditLog::class);
378+
return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects');
418379
}
419380

420381
/**

0 commit comments

Comments
 (0)