Skip to content

Commit 5bb66a0

Browse files
committed
Add new activity logging code to replace audit log
1 parent c14c7b4 commit 5bb66a0

File tree

11 files changed

+534
-0
lines changed

11 files changed

+534
-0
lines changed

app/Facades/Activity.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Pterodactyl\Facades;
4+
5+
use Illuminate\Support\Facades\Facade;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Pterodactyl\Services\Activity\ActivityLogService;
8+
9+
/**
10+
* @method static ActivityLogService anonymous()
11+
* @method static ActivityLogService event(string $action)
12+
* @method static ActivityLogService withDescription(?string $description)
13+
* @method static ActivityLogService withSubject(Model $subject)
14+
* @method static ActivityLogService withActor(Model $actor)
15+
* @method static ActivityLogService withProperties(\Illuminate\Support\Collection|array $properties)
16+
* @method static ActivityLogService withProperty(string $key, mixed $value)
17+
* @method static mixed transaction(\Closure $callback)
18+
*/
19+
class Activity extends Facade
20+
{
21+
protected static function getFacadeAccessor()
22+
{
23+
return ActivityLogService::class;
24+
}
25+
}

app/Facades/LogBatch.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Pterodactyl\Facades;
4+
5+
use Illuminate\Support\Facades\Facade;
6+
use Pterodactyl\Services\Activity\AcitvityLogBatchService;
7+
8+
/**
9+
* @method static ?string uuid()
10+
* @method static void start()
11+
* @method static void end()
12+
* @method static mixed transaction(\Closure $callback)
13+
*/
14+
class LogBatch extends Facade
15+
{
16+
protected static function getFacadeAccessor()
17+
{
18+
return AcitvityLogBatchService::class;
19+
}
20+
}

app/Facades/LogTarget.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Pterodactyl\Facades;
4+
5+
use Illuminate\Support\Facades\Facade;
6+
use Pterodactyl\Services\Activity\ActivityLogTargetableService;
7+
8+
/**
9+
* @method static void setActor(\Illuminate\Database\Eloquent\Model $actor)
10+
* @method static void setSubject(\Illuminate\Database\Eloquent\Model $subject)
11+
* @method static \Illuminate\Database\Eloquent\Model|null actor()
12+
* @method static \Illuminate\Database\Eloquent\Model|null subject()
13+
* @method static void reset()
14+
*/
15+
class LogTarget extends Facade
16+
{
17+
protected static function getFacadeAccessor()
18+
{
19+
return ActivityLogTargetableService::class;
20+
}
21+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Pterodactyl\Models\Server;
8+
use Pterodactyl\Facades\LogTarget;
9+
10+
class ServerActivityLogs
11+
{
12+
/**
13+
* Attempts to automatically scope all of the activity log events registered
14+
* within the request instance to the given user and server. This only sets
15+
* the actor and subject if there is a server present on the request.
16+
*
17+
* If no server is found this is a no-op as the activity log service can always
18+
* set the user based on the authmanager response.
19+
*/
20+
public function handle(Request $request, Closure $next)
21+
{
22+
$server = $request->route()->parameter('server');
23+
if ($server instanceof Server) {
24+
LogTarget::setActor($request->user());
25+
LogTarget::setSubject($server);
26+
}
27+
28+
return $next($request);
29+
}
30+
}

app/Models/ActivityLog.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
namespace Pterodactyl\Models;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use Illuminate\Database\Eloquent\Relations\MorphTo;
7+
use Illuminate\Database\Eloquent\Model as IlluminateModel;
8+
9+
/**
10+
* \Pterodactyl\Models\ActivityLog.
11+
*
12+
* @property int $id
13+
* @property string|null $batch
14+
* @property string $event
15+
* @property string|null $description
16+
* @property string|null $actor_type
17+
* @property int|null $actor_id
18+
* @property string|null $subject_type
19+
* @property int|null $subject_id
20+
* @property \Illuminate\Support\Collection $properties
21+
* @property string $timestamp
22+
* @property \Illuminate\Database\Eloquent\Model|\Eloquent $actor
23+
* @property \Illuminate\Database\Eloquent\Model|\Eloquent $subject
24+
*
25+
* @method static Builder|ActivityLog forAction(string $action)
26+
* @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor)
27+
* @method static Builder|ActivityLog forSubject(\Illuminate\Database\Eloquent\Model $subject)
28+
* @method static Builder|ActivityLog newModelQuery()
29+
* @method static Builder|ActivityLog newQuery()
30+
* @method static Builder|ActivityLog query()
31+
* @method static Builder|ActivityLog whereAction($value)
32+
* @method static Builder|ActivityLog whereActorId($value)
33+
* @method static Builder|ActivityLog whereActorType($value)
34+
* @method static Builder|ActivityLog whereBatch($value)
35+
* @method static Builder|ActivityLog whereDescription($value)
36+
* @method static Builder|ActivityLog whereId($value)
37+
* @method static Builder|ActivityLog whereIp($value)
38+
* @method static Builder|ActivityLog whereProperties($value)
39+
* @method static Builder|ActivityLog whereSubjectId($value)
40+
* @method static Builder|ActivityLog whereSubjectType($value)
41+
* @method static Builder|ActivityLog whereTimestamp($value)
42+
* @mixin \Eloquent
43+
*/
44+
class ActivityLog extends Model
45+
{
46+
public $timestamps = false;
47+
48+
protected $guarded = [
49+
'id',
50+
'timestamp',
51+
];
52+
53+
protected $casts = [
54+
'properties' => 'collection',
55+
];
56+
57+
public static $validationRules = [
58+
'event' => ['required', 'string'],
59+
'batch' => ['nullable', 'uuid'],
60+
'description' => ['nullable', 'string'],
61+
'properties' => ['nullable', 'array'],
62+
];
63+
64+
public function actor(): MorphTo
65+
{
66+
return $this->morphTo();
67+
}
68+
69+
public function subject(): MorphTo
70+
{
71+
return $this->morphTo();
72+
}
73+
74+
public function scopeForAction(Builder $builder, string $action): Builder
75+
{
76+
return $builder->where('action', $action);
77+
}
78+
79+
/**
80+
* Scopes a query to only return results where the actor is a given model.
81+
*/
82+
public function scopeForActor(Builder $builder, IlluminateModel $actor): Builder
83+
{
84+
return $builder->whereMorphedTo('actor', $actor);
85+
}
86+
87+
/**
88+
* Scopes a query to only return results where the subject is the given model.
89+
*/
90+
public function scopeForSubject(Builder $builder, IlluminateModel $subject): Builder
91+
{
92+
return $builder->whereMorphedTo('subject', $subject);
93+
}
94+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Pterodactyl\Providers;
4+
5+
use Illuminate\Support\ServiceProvider;
6+
use Pterodactyl\Services\Activity\AcitvityLogBatchService;
7+
use Pterodactyl\Services\Activity\ActivityLogTargetableService;
8+
9+
class ActivityLogServiceProvider extends ServiceProvider
10+
{
11+
/**
12+
* Registers the necessary activity logger singletons scoped to the individual
13+
* request instances.
14+
*/
15+
public function register()
16+
{
17+
$this->app->scoped(AcitvityLogBatchService::class);
18+
$this->app->scoped(ActivityLogTargetableService::class);
19+
}
20+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace Pterodactyl\Services\Activity;
4+
5+
use Ramsey\Uuid\Uuid;
6+
7+
class AcitvityLogBatchService
8+
{
9+
protected int $transaction = 0;
10+
protected ?string $uuid = null;
11+
12+
/**
13+
* Returns the UUID of the batch, or null if there is not a batch currently
14+
* being executed.
15+
*/
16+
public function uuid(): ?string
17+
{
18+
return $this->uuid;
19+
}
20+
21+
/**
22+
* Starts a new batch transaction. If there is already a transaction present
23+
* this will be nested.
24+
*/
25+
public function start(): void
26+
{
27+
if ($this->transaction === 0) {
28+
$this->uuid = Uuid::uuid4()->toString();
29+
}
30+
31+
++$this->transaction;
32+
}
33+
34+
/**
35+
* Ends a batch transaction, if this is the last transaction in the stack
36+
* the UUID will be cleared out.
37+
*/
38+
public function end(): void
39+
{
40+
$this->transaction = max(0, $this->transaction - 1);
41+
42+
if ($this->transaction === 0) {
43+
$this->uuid = null;
44+
}
45+
}
46+
47+
/**
48+
* Executes the logic provided within the callback in the scope of an activity
49+
* log batch transaction.
50+
*
51+
* @param \Closure $callback
52+
* @return mixed
53+
*/
54+
public function transaction(\Closure $callback)
55+
{
56+
$this->start();
57+
$result = $callback($this->uuid());
58+
$this->end();
59+
60+
return $result;
61+
}
62+
}

0 commit comments

Comments
 (0)