mirror of
https://github.com/pelican-dev/panel.git
synced 2026-05-04 18:00:48 +03:00
Fix listener to accept Filament event objects
- Change handle() signature to accept RecordCreated|RecordUpdated event objects instead of separate params (Laravel passes the event, not constructor args) - Use [REDACTED] placeholder instead of silently dropping sensitive fields - Expand redacted fields list with current_password, daemon_token, _token - Update tests to construct proper event objects
This commit is contained in:
@@ -4,8 +4,8 @@ namespace App\Listeners;
|
||||
|
||||
use App\Facades\Activity;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\Page;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Filament\Resources\Events\RecordCreated;
|
||||
use Filament\Resources\Events\RecordUpdated;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AdminActivityListener
|
||||
@@ -13,23 +13,29 @@ class AdminActivityListener
|
||||
protected const REDACTED_FIELDS = [
|
||||
'password',
|
||||
'password_confirmation',
|
||||
'current_password',
|
||||
'token',
|
||||
'secret',
|
||||
'api_key',
|
||||
'daemon_token',
|
||||
'_token',
|
||||
];
|
||||
|
||||
/** @param array<string, mixed> $data */
|
||||
public function handle(Model $record, array $data, Page $page): void
|
||||
public function handle(RecordCreated|RecordUpdated $event): void
|
||||
{
|
||||
if (Filament::getCurrentPanel()?->getId() !== 'admin') {
|
||||
return;
|
||||
}
|
||||
|
||||
$record = $event->getRecord();
|
||||
$page = $event->getPage();
|
||||
$data = $event->getData();
|
||||
|
||||
$resourceClass = $page::getResource();
|
||||
$modelClass = $resourceClass::getModel();
|
||||
$slug = Str::kebab(class_basename($modelClass));
|
||||
|
||||
$action = $page instanceof \Filament\Resources\Pages\CreateRecord ? 'create' : 'update';
|
||||
$action = $event instanceof RecordCreated ? 'create' : 'update';
|
||||
|
||||
$properties = $this->redactSensitiveFields($data);
|
||||
|
||||
@@ -49,6 +55,8 @@ class AdminActivityListener
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (in_array($key, self::REDACTED_FIELDS, true)) {
|
||||
$redacted[$key] = '[REDACTED]';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ use App\Models\Egg;
|
||||
use App\Models\Node;
|
||||
use App\Models\Role;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Events\RecordCreated;
|
||||
use Filament\Resources\Events\RecordUpdated;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
function pageInstance(string $class): object
|
||||
@@ -17,6 +19,16 @@ function pageInstance(string $class): object
|
||||
return (new ReflectionClass($class))->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
function createEvent(object $record, array $data, object $page): RecordCreated
|
||||
{
|
||||
return new RecordCreated($record, $data, $page);
|
||||
}
|
||||
|
||||
function updateEvent(object $record, array $data, object $page): RecordUpdated
|
||||
{
|
||||
return new RecordUpdated($record, $data, $page);
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
[$this->admin] = generateTestAccount([]);
|
||||
$this->admin = $this->admin->syncRoles(Role::getRootAdmin());
|
||||
@@ -29,7 +41,7 @@ it('logs create activity for an egg', function () {
|
||||
$egg = Egg::first();
|
||||
|
||||
$listener = new AdminActivityListener();
|
||||
$listener->handle($egg, ['name' => 'Test Egg'], pageInstance(CreateEgg::class));
|
||||
$listener->handle(createEvent($egg, ['name' => 'Test Egg'], pageInstance(CreateEgg::class)));
|
||||
|
||||
$this->assertActivityLogged('admin:egg.create');
|
||||
});
|
||||
@@ -38,7 +50,7 @@ it('logs update activity for an egg', function () {
|
||||
$egg = Egg::first();
|
||||
|
||||
$listener = new AdminActivityListener();
|
||||
$listener->handle($egg, ['name' => 'Updated Egg'], pageInstance(EditEgg::class));
|
||||
$listener->handle(updateEvent($egg, ['name' => 'Updated Egg'], pageInstance(EditEgg::class)));
|
||||
|
||||
$this->assertActivityLogged('admin:egg.update');
|
||||
});
|
||||
@@ -47,7 +59,7 @@ it('logs create activity for a node', function () {
|
||||
$node = Node::first();
|
||||
|
||||
$listener = new AdminActivityListener();
|
||||
$listener->handle($node, ['name' => 'Test Node'], pageInstance(CreateNode::class));
|
||||
$listener->handle(createEvent($node, ['name' => 'Test Node'], pageInstance(CreateNode::class)));
|
||||
|
||||
$this->assertActivityLogged('admin:node.create');
|
||||
});
|
||||
@@ -56,7 +68,7 @@ it('logs update activity for a node', function () {
|
||||
$node = Node::first();
|
||||
|
||||
$listener = new AdminActivityListener();
|
||||
$listener->handle($node, ['name' => 'Updated Node'], pageInstance(EditNode::class));
|
||||
$listener->handle(updateEvent($node, ['name' => 'Updated Node'], pageInstance(EditNode::class)));
|
||||
|
||||
$this->assertActivityLogged('admin:node.update');
|
||||
});
|
||||
@@ -67,7 +79,7 @@ it('does not log activity for non-admin panels', function () {
|
||||
$egg = Egg::first();
|
||||
|
||||
$listener = new AdminActivityListener();
|
||||
$listener->handle($egg, ['name' => 'Test'], pageInstance(CreateEgg::class));
|
||||
$listener->handle(createEvent($egg, ['name' => 'Test'], pageInstance(CreateEgg::class)));
|
||||
|
||||
Event::assertNotDispatched(ActivityLogged::class);
|
||||
});
|
||||
@@ -76,7 +88,7 @@ it('sets the record as the activity subject', function () {
|
||||
$egg = Egg::first();
|
||||
|
||||
$listener = new AdminActivityListener();
|
||||
$listener->handle($egg, ['name' => 'Test'], pageInstance(CreateEgg::class));
|
||||
$listener->handle(createEvent($egg, ['name' => 'Test'], pageInstance(CreateEgg::class)));
|
||||
|
||||
$this->assertActivityFor('admin:egg.create', $this->admin, $egg);
|
||||
});
|
||||
@@ -94,17 +106,17 @@ it('redacts sensitive fields from activity properties', function () {
|
||||
];
|
||||
|
||||
$listener = new AdminActivityListener();
|
||||
$listener->handle($egg, $data, pageInstance(EditEgg::class));
|
||||
$listener->handle(updateEvent($egg, $data, pageInstance(EditEgg::class)));
|
||||
|
||||
Event::assertDispatched(ActivityLogged::class, function (ActivityLogged $event) {
|
||||
$properties = $event->model->properties;
|
||||
|
||||
expect($properties)->toHaveKey('name', 'Visible')
|
||||
->not->toHaveKey('password')
|
||||
->not->toHaveKey('password_confirmation')
|
||||
->not->toHaveKey('token')
|
||||
->not->toHaveKey('secret')
|
||||
->not->toHaveKey('api_key');
|
||||
->toHaveKey('password', '[REDACTED]')
|
||||
->toHaveKey('password_confirmation', '[REDACTED]')
|
||||
->toHaveKey('token', '[REDACTED]')
|
||||
->toHaveKey('secret', '[REDACTED]')
|
||||
->toHaveKey('api_key', '[REDACTED]');
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -123,14 +135,14 @@ it('redacts sensitive fields in nested arrays', function () {
|
||||
];
|
||||
|
||||
$listener = new AdminActivityListener();
|
||||
$listener->handle($egg, $data, pageInstance(EditEgg::class));
|
||||
$listener->handle(updateEvent($egg, $data, pageInstance(EditEgg::class)));
|
||||
|
||||
Event::assertDispatched(ActivityLogged::class, function (ActivityLogged $event) {
|
||||
$properties = $event->model->properties;
|
||||
|
||||
expect($properties['nested'])->toHaveKey('safe', 'value')
|
||||
->not->toHaveKey('password')
|
||||
->not->toHaveKey('token');
|
||||
->toHaveKey('password', '[REDACTED]')
|
||||
->toHaveKey('token', '[REDACTED]');
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -140,7 +152,7 @@ it('generates kebab-case event names from model class names', function () {
|
||||
$node = Node::first();
|
||||
|
||||
$listener = new AdminActivityListener();
|
||||
$listener->handle($node, ['name' => 'Test'], pageInstance(CreateNode::class));
|
||||
$listener->handle(createEvent($node, ['name' => 'Test'], pageInstance(CreateNode::class)));
|
||||
|
||||
$this->assertActivityLogged('admin:node.create');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user