1
0
mirror of https://github.com/Kovah/LinkAce.git synced 2025-01-17 13:18:21 +01:00

Add audit logs for users (#467)

This commit is contained in:
Kovah 2022-06-10 14:28:53 +02:00
parent 6c35874ecc
commit 6960ddf99e
No known key found for this signature in database
GPG Key ID: AAAA031BA9830D7B
8 changed files with 187 additions and 4 deletions

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\App;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use App\Models\User;
use OwenIt\Auditing\Models\Audit;
class AuditController extends Controller
@ -11,10 +12,14 @@ class AuditController extends Controller
public function __invoke()
{
$settingsHistory = Audit::where('auditable_type', Setting::class)->with('auditable')
->latest()->paginate(pageName: 'settings');
->latest()->paginate(pageName: 'settings_page');
$userHistory = Audit::where('auditable_type', User::class)->with('auditable')
->latest()->paginate(pageName: 'user_page');
return view('app.audit-logs', [
'settings_history' => $settingsHistory,
'user_history' => $userHistory,
]);
}
}

View File

@ -9,6 +9,8 @@ use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Laravel\Fortify\TwoFactorAuthenticatable;
use OwenIt\Auditing\Auditable as AuditableTrait;
use OwenIt\Auditing\Contracts\Auditable;
/**
* Class User
@ -25,8 +27,9 @@ use Laravel\Fortify\TwoFactorAuthenticatable;
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*/
class User extends Authenticatable
class User extends Authenticatable implements Auditable
{
use AuditableTrait;
use Notifiable;
use HasFactory;
use TwoFactorAuthenticatable;
@ -41,8 +44,28 @@ class User extends Authenticatable
protected $hidden = [
'password',
'remember_token',
'api_token',
];
/*
* ========================================================================
* AUDIT SETTINGS
*/
protected array $auditEvents = [
'created',
'updated',
'deleted',
'restored',
];
protected array $auditInclude = [
'name',
'email',
];
public static array $auditModifiers = [];
/*
* ========================================================================
* RELATIONSHIPS

View File

@ -0,0 +1,88 @@
<?php
namespace App\View\Components\History;
use App\Models\User;
use Illuminate\View\Component;
use OwenIt\Auditing\Models\Audit;
class UserEntry extends Component
{
public function __construct(private Audit $entry, private array $changes = [])
{
}
public function render()
{
$timestamp = formatDateTime($this->entry->created_at);
if ($this->entry->event === 'deleted') {
$this->changes[] = trans('user.history_deleted', ['name' => $this->entry->getModified()['name']['old']]);
} elseif ($this->entry->event === 'restored') {
$this->changes[] = trans('user.history_restored', ['name' => $this->entry->getModified()['name']['new']]);
} elseif ($this->entry->event === 'created') {
$this->changes[] = trans('user.history_created', ['name' => $this->entry->getModified()['name']['new']]);
} else {
foreach ($this->entry->getModified() as $field => $change) {
$this->processChange($field, $change);
}
}
return view('components.history-entry', [
'timestamp' => $timestamp,
'changes' => $this->changes,
]);
}
protected function processChange(string $field, array $changeData): void
{
$fieldName = trans('user.' . $field);
[$oldValue, $newValue] = $this->processValues($field, $changeData);
if ($oldValue === null) {
$change = trans('linkace.history_added', [
'fieldname' => $fieldName,
'newvalue' => htmlspecialchars($newValue),
]);
} elseif ($newValue === null) {
$change = trans('linkace.history_removed', [
'fieldname' => $fieldName,
'oldvalue' => htmlspecialchars($oldValue),
]);
} else {
$change = trans('linkace.history_changed', [
'fieldname' => $fieldName,
'oldvalue' => htmlspecialchars($oldValue),
'newvalue' => htmlspecialchars($newValue),
]);
}
$this->changes[] = trans('audit.user_history_entry', [
'id' => $this->entry->auditable->id,
'change' => $change,
]);
}
/**
* Apply specialized methods for different fields to handle particular
* formatting needs of these fields.
*
* @param string $field
* @param array $changeData
* @return array
*/
protected function processValues(string $field, array $changeData): array
{
$oldValue = $changeData['old'] ?? null;
$newValue = $changeData['new'] ?? null;
if (isset(User::$auditModifiers[$field])) {
$modifier = app(User::$auditModifiers[$field]);
$oldValue = $modifier->modify($oldValue);
$newValue = $modifier->modify($newValue);
return [$oldValue, $newValue];
}
return [$oldValue, $newValue];
}
}

View File

@ -165,5 +165,5 @@ return [
|
*/
'console' => env('AUDIT_CONSOLE_EVENTS', false),
'console' => env('AUDIT_CONSOLE_EVENTS', true),
];

View File

@ -4,5 +4,7 @@ return [
'log' => 'Audit Log',
'settings_history' => 'Settings History',
'user_history' => 'User History',
'user_history_entry' => 'User :id: :change',
];

View File

@ -4,8 +4,13 @@ return [
'users' => 'Users',
'username' => 'Username',
'name' => 'Username',
'email' => 'Email',
'history_deleted' => 'User <code>:name</code> was deleted',
'history_restored' => 'User <code>:name</code> was restored',
'history_created' => 'User <code>:name</code> was created',
'hello' => 'Hello :user!',
'for_user' => 'for User',
];

View File

@ -8,7 +8,7 @@
</div>
<div class="card-body">
<div class="history mb-6">
<div class="history mb-4">
@foreach($settings_history as $entry)
<x-history.settings-entry :entry="$entry"/>
@endforeach
@ -19,4 +19,21 @@
</div>
</div>
<div class="card mt-4">
<div class="card-header">
@lang('audit.user_history')
</div>
<div class="card-body">
<div class="history mb-4">
@foreach($user_history as $entry)
<x-history.user-entry :entry="$entry"/>
@endforeach
</div>
{!! $user_history->onEachSide(1)->links() !!}
</div>
</div>
@endsection

View File

@ -0,0 +1,43 @@
<?php
namespace Tests\Components\History;
use App\Models\User;
use App\View\Components\History\UserEntry;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserEntryTest extends TestCase
{
use RefreshDatabase;
public function testRegularChange(): void
{
$user = User::factory()->create(['name' => 'TestUser']);
$user->update(['name' => 'UserTest']);
$historyEntries = $user->audits()->latest()->get();
$output = (new UserEntry($historyEntries[0]))->render();
$this->assertStringContainsString('User <code>TestUser</code> was created', $output);
$output = (new UserEntry($historyEntries[1]))->render();
$this->assertStringContainsString(
'User 1: Changed Username from <code>TestUser</code> to <code>UserTest</code>',
$output
);
}
public function testModelDeletion(): void
{
$user = User::factory()->create(['name' => 'TestUser']);
$user->delete();
$historyEntries = $user->audits()->latest()->get();
$output = (new UserEntry($historyEntries[1]))->render();
$this->assertStringContainsString('User <code>TestUser</code> was deleted', $output);
}
}