Files
timebank-cc-public/app/Console/Commands/CleanupIpAddresses.php
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

185 lines
6.4 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Admin;
use App\Models\Bank;
use App\Models\Organization;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Spatie\Activitylog\Models\Activity;
class CleanupIpAddresses extends Command
{
protected $signature = 'ip:cleanup {--dry-run : Show what would be cleaned without actually deleting}';
protected $description;
public function __construct()
{
parent::__construct();
$this->description = 'Anonymize IP addresses older than ' .
timebank_config('ip_retention.retention_days') . ' days for GDPR compliance';
}
public function handle()
{
$retentionDays = timebank_config('ip_retention.retention_days', 180);
$cutoffDate = now()->subDays($retentionDays);
$isDryRun = $this->option('dry-run');
$this->info('Starting IP address cleanup...');
$this->info('Retention period: ' . $retentionDays . ' days');
$this->info('Cutoff date: ' . $cutoffDate->toDateTimeString());
if ($isDryRun) {
$this->warn('DRY RUN MODE - No changes will be made');
}
$totalAnonymized = 0;
// Cleanup profile tables (users, organizations, banks, admins)
$profileModels = [
'users' => User::class,
'organizations' => Organization::class,
'banks' => Bank::class,
'admins' => Admin::class,
];
foreach ($profileModels as $tableName => $modelClass) {
$count = $this->cleanupProfileTable($tableName, $modelClass, $cutoffDate, $isDryRun);
$totalAnonymized += $count;
}
// Cleanup activity log IP addresses
$activityLogCount = $this->cleanupActivityLog($cutoffDate, $isDryRun);
$totalAnonymized += $activityLogCount;
$action = $isDryRun ? 'Would anonymize' : 'Anonymized';
$this->info("{$action} {$totalAnonymized} IP address records in total.");
// Log the cleanup action
if (!$isDryRun) {
Log::info('IP address cleanup completed', [
'retention_days' => $retentionDays,
'cutoff_date' => $cutoffDate->toDateTimeString(),
'total_anonymized' => $totalAnonymized,
]);
}
return 0;
}
/**
* Cleanup IP addresses from profile tables
*
* @param string $tableName
* @param string $modelClass
* @param \Carbon\Carbon $cutoffDate
* @param bool $isDryRun
* @return int Number of records anonymized
*/
protected function cleanupProfileTable(string $tableName, string $modelClass, $cutoffDate, bool $isDryRun): int
{
$this->line("\nProcessing {$tableName} table...");
// Find profiles with last_login_ip that should be anonymized
$query = $modelClass::whereNotNull('last_login_ip')
->where('last_login_ip', '!=', '')
->where(function ($q) use ($cutoffDate) {
// Anonymize if last_login_at is older than cutoff date
$q->where('last_login_at', '<', $cutoffDate)
// Or if last_login_at is null (should not happen, but handle it)
->orWhereNull('last_login_at');
});
$count = $query->count();
if ($count === 0) {
$this->line(" No IP addresses to anonymize in {$tableName}");
return 0;
}
if ($isDryRun) {
$this->warn(" Would anonymize {$count} IP addresses in {$tableName}");
// Show some examples in dry run
$examples = $query->take(3)->get(['id', 'name', 'last_login_ip', 'last_login_at']);
if ($examples->isNotEmpty()) {
$this->line(" Examples:");
foreach ($examples as $example) {
$loginDate = 'never';
if ($example->last_login_at) {
$loginDate = is_string($example->last_login_at)
? $example->last_login_at
: $example->last_login_at->toDateString();
}
$this->line(" - ID {$example->id} ({$example->name}): {$example->last_login_ip} (last login: {$loginDate})");
}
}
} else {
// Anonymize by setting to null
$updated = $query->update(['last_login_ip' => null]);
$this->info(" ✓ Anonymized {$updated} IP addresses in {$tableName}");
}
return $count;
}
/**
* Cleanup IP addresses from activity log
*
* @param \Carbon\Carbon $cutoffDate
* @param bool $isDryRun
* @return int Number of records anonymized
*/
protected function cleanupActivityLog($cutoffDate, bool $isDryRun): int
{
$this->line("\nProcessing activity_log table...");
// Find activity logs with IP addresses older than cutoff date
$query = Activity::whereNotNull('properties->ip')
->where('created_at', '<', $cutoffDate);
$count = $query->count();
if ($count === 0) {
$this->line(" No IP addresses to anonymize in activity_log");
return 0;
}
if ($isDryRun) {
$this->warn(" Would anonymize {$count} IP addresses in activity_log");
// Show some examples in dry run
$examples = $query->take(3)->get(['id', 'log_name', 'properties', 'created_at']);
if ($examples->isNotEmpty()) {
$this->line(" Examples:");
foreach ($examples as $example) {
$ip = $example->properties['ip'] ?? 'N/A';
$this->line(" - ID {$example->id} ({$example->log_name}): {$ip} (date: {$example->created_at->toDateString()})");
}
}
} else {
// Anonymize by removing IP from properties JSON
$activities = $query->get();
$updated = 0;
foreach ($activities as $activity) {
$properties = $activity->properties;
if (isset($properties['ip'])) {
unset($properties['ip']);
$activity->properties = $properties;
$activity->save();
$updated++;
}
}
$this->info(" ✓ Anonymized {$updated} IP addresses in activity_log");
}
return $count;
}
}