Anonymisation
Lorsque vous devez effacer des donnees personnelles tout en preservant le journal d'audit, la suppression definitive d'un enregistrement utilisateur est souvent impraticable — les cles etrangeres, l'historique d'audit et la logique metier dependent de l'existence de l'enregistrement. AuditChain fournit une methode anonymize() qui remplace les donnees personnelles par une valeur de substitution, effacant ainsi les donnees personnelles tout en preservant l'enregistrement et ses relations. Cela repond directement a l'article 17 du RGPD (le "droit a l'oubli") et aux exigences d'effacement similaires d'autres reglementations.
Utilisation de base
Appelez anonymize() sur n'importe quel model auditable pour remplacer tous les champs de donnees personnelles annotes par la chaine de remplacement configuree :
$user->anonymize(); // Before: email => 'john@example.com', name => 'John Doe', age => 35 // After: email => '[ANONYMIZED]-a1b2c3d4', name => '[ANONYMIZED]-e5f6g7h8', age => null
L'anonymisation est sensible au type : les champs de type chaîne sont remplacés par la chaîne de remplacement configurée suivie d'un suffixe aléatoire de 8 caractères (via Str::random(8)). Les champs de type non-chaîne reçoivent null. Le suffixe aléatoire évite les violations de contrainte UNIQUE lors de l'anonymisation de plusieurs enregistrements — sans lui, deux utilisateurs anonymisés auraient tous deux email => '[ANONYMIZED]', ce qui échouerait sur un index unique.
Fonctionnement interne
En interne, anonymize() :
- Lit la chaine de remplacement configuree dans
audit-chain.anonymization.replacement - Obtient la liste des champs de donnees personnelles via
getPersonalDataFields() - Pour chaque champ de donnees personnelles non nul :
- Champs de type chaîne : remplacés par
-{random8}(viaStr::random(8)) - Champs de type non-chaîne : définis à
null
- Champs de type chaîne : remplacés par
- Sauvegarde le model avec
saveQuietly()
L'utilisation de saveQuietly() est deliberee — elle empeche la sauvegarde de declencher les evenements Eloquent, ce qui signifie qu'aucune entree de journal d'audit n'est creee pour l'anonymisation elle-meme. C'est le comportement correct : vous ne souhaitez pas que le journal d'audit enregistre la transition des donnees reelles vers les donnees anonymisees, car cela exposerait encore les donnees personnelles originales dans la colonne old_values.
Configuration
Personnalisez la chaine de remplacement dans config/audit-chain.php :
'anonymization' => [ 'replacement' => '[ANONYMIZED]', // default ],
Ou surchargez-la par environnement :
'anonymization' => [ 'replacement' => '[REDACTED]', ],
Les champs nuls sont ignores
Si un champ de donnees personnelles est deja null, anonymize() le laisse a null plutot que de le remplacer. Cela preserve la distinction entre "jamais fourni" et "a ete fourni mais a ete anonymise".
Exemple pratique
Un workflow typique de demande d'effacement :
use Illuminate\Http\JsonResponse; class ErasureController extends Controller { public function destroy(User $user): JsonResponse { $this->authorize('delete', $user); // Export data first (Article 20 - data portability) $export = $user->exportFullSubjectData(); // Anonymize personal data (Article 17 - right to erasure) $user->anonymize(); // Optionally record that an erasure was performed $user->audit('erasure_completed'); return response()->json([ 'message' => 'Personal data has been anonymized.', 'export' => $export, ]); } }
Note : L'appel
audit('erasure_completed')ci-dessus est sans risque — il enregistre un evenement personnalise sur le model deja anonymise, de sorte qu'aucune donnee personnelle n'apparait dans l'entree du journal d'audit.
Anonymiser les journaux d'audit
En plus d'anonymiser le model lui-même, vous pouvez anonymiser les données personnelles stockées dans les entrées du journal d'audit via anonymizeAuditLogs(). Cette méthode fait partie de l'interface Auditable et traite différemment les logs légers et chaînés :
- Logs légers (où
hashestNULL) : les champs de données personnelles sont remplacés directement dans les colonnesold_valuesetnew_values. - Logs chaînés (où
hashn'est pasNULL) : pour préserver l'intégrité de la chaîne, les valeurs ne sont pas modifiées. Au lieu de cela, les noms des champs sont stockés dans la colonneanonymized_fieldset remplacés au moment de la lecture.
// Anonymiser les données personnelles du model $user->anonymize(); // Anonymiser aussi les données personnelles dans les entrées du journal d'audit $user->anonymizeAuditLogs();
Cela garantit que les entrées historiques du journal d'audit n'exposent plus de données personnelles, tandis que les entrées chaînées restent vérifiables.
Note :
exportFullSubjectData()respecte automatiquement la colonneanonymized_fieldset remplace ces champs dans les données exportées.
Anonymisation en masse
Anonymisez plusieurs enregistrements dans une boucle. Le suffixe aléatoire garantit l'absence de conflits de contrainte UNIQUE :
$users = User::where('deleted_at', '<', now()->subYear())->get(); foreach ($users as $user) { $user->anonymize(); } // user 1: email => '[ANONYMIZED]-a1b2c3d4', name => '[ANONYMIZED]-e5f6g7h8' // user 2: email => '[ANONYMIZED]-x9y8z7w6', name => '[ANONYMIZED]-m3n4o5p6' // user 3: email => '[ANONYMIZED]-q1r2s3t4', name => '[ANONYMIZED]-u5v6w7x8'
Conservation du journal d'audit
L'anonymisation remplace les donnees personnelles sur le model mais — sauf si vous appelez anonymizeAuditLogs() — ne modifie pas les entrees existantes du journal d'audit. En fonction de votre politique de conservation des donnees et de vos obligations legales, vous devrez peut-etre egalement purger les anciens journaux d'audit :
php artisan audit:prune --days=90
Consultez Purge des journaux pour plus de details sur la configuration des periodes de conservation.