Immutabilite
Un journal d'audit n'est digne de confiance que s'il ne peut pas etre altere apres coup. AuditChain impose l'immutabilite a travers plusieurs couches — des gardes du model Eloquent aux restrictions au niveau base de donnees — de sorte qu'aucun point de defaillance unique ne puisse compromettre vos donnees d'audit.
Gardes Eloquent Boot
Le model AuditLog enregistre des ecouteurs d'evenements updating et deleting dans sa methode booted(). Les deux lancent une RuntimeException, empechant toute modification ou suppression via Eloquent :
protected static function booted(): void { static::updating(function (): void { throw new RuntimeException('Audit logs are immutable and cannot be updated.'); }); static::deleting(function (): void { throw new RuntimeException('Audit logs are immutable and cannot be deleted.'); }); }
Cela signifie que tout code tentant d'appeler $auditLog->update(...), $auditLog->save() (apres modification d'attributs) ou $auditLog->delete() echouera immediatement avec une exception. C'est la premiere ligne de defense et elle intercepte les modifications accidentelles pendant le fonctionnement normal de l'application.
Liste blanche $fillable stricte
Le model AuditLog definit un tableau $fillable explicite qui ne liste que les attributs definis lors de la creation :
protected $fillable = [ 'auditable_type', 'auditable_id', 'event', 'user_id', 'ip_address', 'user_agent', 'old_values', 'new_values', 'personal_data_accessed', 'batch_uuid', 'context', 'prev_hash', 'hash', 'created_at', ];
Cela empeche l'assignation de masse d'attributs au-dela de ce qu'AuditChain definit explicitement lors de AuditLog::create(). Bien que les gardes boot bloquent deja les mises a jour, la liste fillable stricte ajoute une seconde couche qui limite ce qui peut etre ecrit meme lors de la creation.
Permissions au niveau base de donnees
Les gardes Eloquent protegent contre les modifications effectuees via l'ORM, mais elles ne peuvent pas empecher les requetes SQL brutes. Pour une veritable immutabilite, utilisez un utilisateur base de donnees dedie avec des droits INSERT et SELECT uniquement sur la table d'audit.
Consultez Permissions de base de donnees pour les instructions de mise en place et les exemples de commandes GRANT.
Horodatages UTC
Tous les horodatages des journaux d'audit sont normalises en UTC avant le stockage :
now()->utc()->toDateTimeString()
C'est essentiel pour l'integrite de la chaine de hash. L'horodatage created_at est inclus dans le calcul du hash, donc toute variation de format ou de fuseau horaire produirait un hash different et casserait la verification. En stockant toujours en UTC, AuditChain garantit que :
- Les hash sont deterministes quel que soit le fuseau horaire du serveur.
- La verification produit le meme resultat, qu'elle soit executee localement ou sur un serveur dans un fuseau horaire different.
- Les comparaisons d'horodatages dans les requetes se comportent de maniere coherente.
Lors de la verification, les horodatages sont re-parses via Carbon::parse($timestamp, 'UTC')->toDateTimeString() (avec fuseau horaire UTC explicite) pour garantir que la meme normalisation est appliquee.
Ce que chaque couche protege
| Menace | Gardes Eloquent | $fillable |
Permissions BD | Chaine de hash |
|---|---|---|---|---|
$auditLog->update(...) |
Oui | -- | -- | Detecte |
$auditLog->delete() |
Oui | -- | -- | Detecte |
| Assignation de masse de champs supplementaires | -- | Oui | -- | -- |
DB::statement('UPDATE ...') |
-- | -- | Oui | Detecte |
DB::statement('DELETE ...') |
-- | -- | Oui | Detecte |
| Acces direct via un outil base de donnees | -- | -- | Oui | Detecte |
| Chaine de remplacement forgee | -- | -- | -- | Detecte* |
*Necessite un seed de chaine secret pour empecher le recalcul du hash de genese.
Compatibilite avec Octane
AuditChainManager utilise un binding scoped() au lieu d'un singleton(). Cela signifie que le manager est réinitialisé à chaque requête dans les environnements Laravel Octane, empêchant la fuite d'état (batch UUIDs, métadonnées de contexte, flag de désactivation du logging) entre les requêtes.
$timestamps = false
Le model AuditLog definit $timestamps = false pour desactiver la gestion automatique d'updated_at par Laravel. Comme les journaux d'audit ne sont jamais mis a jour, il n'y a pas besoin d'une colonne updated_at. La valeur created_at est definie explicitement par AuditChain au moment de l'enregistrement en utilisant la normalisation UTC, et non par le mecanisme de timestamps d'Eloquent.
Implications pratiques
Vous ne pouvez pas "corriger" une entree de journal d'audit. Si des donnees incorrectes sont enregistrees, l'approche correcte est de creer une nouvelle entree qui documente la correction — et non de modifier l'originale. C'est intentionnel : un journal d'audit immuable doit preserver l'historique complet, y compris les erreurs.
La purge est le seul chemin de suppression pris en charge. La commande audit:prune est le moyen prevu pour supprimer les anciens journaux d'audit. Elle opere en dehors des gardes Eloquent boot (en utilisant des requetes de suppression en masse) et necessite les permissions base de donnees appropriees. Consultez Permissions de base de donnees pour savoir comment gerer cela avec des utilisateurs BD restreints.
Tests
Tests avec les journaux d'audit. Dans les suites de tests, utilisez AuditChain::withoutAudit() pour supprimer la journalisation d'audit pendant le setup et le teardown plutot que d'essayer de supprimer des enregistrements d'audit :
use GrayMatter\AuditChain\Facades\AuditChain; AuditChain::withoutAudit(function () { // Seed test data without creating audit logs User::factory()->count(50)->create(); });