mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-04-16 05:28:37 +02:00
Add authentication log cleaning and associated tests
This commit is contained in:
parent
a9b1a20f30
commit
e73fbf658f
@ -9,6 +9,7 @@
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
@ -215,6 +216,13 @@ public function authentications(Request $request, User $user)
|
|||||||
{
|
{
|
||||||
$this->authorize('view', $user);
|
$this->authorize('view', $user);
|
||||||
|
|
||||||
|
// Here we purge the authentication log.
|
||||||
|
// Running the purge command when someone fetchs the auth log
|
||||||
|
// is not very elegant but it's straitforward compared
|
||||||
|
// to a scheduled task, and the delete query is light.
|
||||||
|
// => To enhance.
|
||||||
|
Artisan::call('2fauth:purge-log');
|
||||||
|
|
||||||
$validated = $this->validate($request, [
|
$validated = $this->validate($request, [
|
||||||
'period' => 'sometimes|numeric',
|
'period' => 'sometimes|numeric',
|
||||||
'limit' => 'sometimes|numeric',
|
'limit' => 'sometimes|numeric',
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
use App\Models\AuthLog;
|
use App\Models\AuthLog;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class PurgeAuthLog extends Command
|
class PurgeAuthLog extends Command
|
||||||
{
|
{
|
||||||
@ -41,17 +42,23 @@ class PurgeAuthLog extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $description = 'Purge all authentication logs older than the configurable amount of days.';
|
public $description = 'Delete all authentication log entries older than the configurable amount of days (see env vars).';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
*/
|
*/
|
||||||
public function handle() : void
|
public function handle() : void
|
||||||
{
|
{
|
||||||
$this->comment('Clearing authentication log...');
|
$retentionTime = config('2fauth.config.authLogRetentionTime');
|
||||||
|
$retentionTime = is_numeric($retentionTime) ? (int) $retentionTime : 365;
|
||||||
|
$date = now()->subDays($retentionTime)->format('Y-m-d H:i:s');
|
||||||
|
|
||||||
$deleted = AuthLog::where('login_at', '<', now()->subDays(config('2fauth.authLogRetentionTime'))->format('Y-m-d H:i:s'))->delete();
|
AuthLog::where('login_at', '<', $date)
|
||||||
|
->orWhere('logout_at', '<', $date)
|
||||||
|
->delete();
|
||||||
|
|
||||||
$this->info($deleted . ' authentication logs cleared.');
|
Log::info('Authentication log purged');
|
||||||
|
|
||||||
|
$this->components->info('Authentication log purged successfully.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
'outgoingProxy' => env('PROXY_FOR_OUTGOING_REQUESTS', ''),
|
'outgoingProxy' => env('PROXY_FOR_OUTGOING_REQUESTS', ''),
|
||||||
'proxyLogoutUrl' => env('PROXY_LOGOUT_URL', null),
|
'proxyLogoutUrl' => env('PROXY_LOGOUT_URL', null),
|
||||||
'appSubdirectory' => env('APP_SUBDIRECTORY', ''),
|
'appSubdirectory' => env('APP_SUBDIRECTORY', ''),
|
||||||
'authLogRetentionTime' => env('AUTHENTICATION_LOG_RETENTION', 365),
|
'authLogRetentionTime' => envUnlessEmpty('AUTHENTICATION_LOG_RETENTION', 365),
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -28,6 +28,24 @@ public function definition()
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model has login before last year.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AuthLog>
|
||||||
|
*/
|
||||||
|
public function daysAgo(int $days)
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) use ($days) {
|
||||||
|
$loginDate = now()->subDays($days);
|
||||||
|
$logoutDate = $loginDate->addHours(1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'login_at' => $loginDate,
|
||||||
|
'logout_at' => $logoutDate,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicate that the model is a failed login.
|
* Indicate that the model is a failed login.
|
||||||
*
|
*
|
||||||
|
142
tests/Feature/Console/PurgeLogTest.php
Normal file
142
tests/Feature/Console/PurgeLogTest.php
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Console;
|
||||||
|
|
||||||
|
use App\Models\AuthLog;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Tests\FeatureTestCase;
|
||||||
|
|
||||||
|
class PurgeLogTest extends FeatureTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
|
||||||
|
*/
|
||||||
|
protected $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function setUp() : void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_purgeLog_completes()
|
||||||
|
{
|
||||||
|
$this->artisan('2fauth:purge-log')
|
||||||
|
->assertSuccessful();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_purgeLog_defaults_to_one_year()
|
||||||
|
{
|
||||||
|
$oneYearOldLog = AuthLog::factory()->daysAgo(366)->for($this->user, 'authenticatable')->create();
|
||||||
|
$sixMonthsOldLog = AuthLog::factory()->daysAgo(364)->for($this->user, 'authenticatable')->create();
|
||||||
|
|
||||||
|
$this->artisan('2fauth:purge-log');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('auth_logs', [
|
||||||
|
'id' => $sixMonthsOldLog->id
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseMissing('auth_logs', [
|
||||||
|
'id' => $oneYearOldLog->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_purgeLog_deletes_records_older_than_retention_time()
|
||||||
|
{
|
||||||
|
$retention = 180;
|
||||||
|
config(['2fauth.config.authLogRetentionTime' => $retention]);
|
||||||
|
$log = AuthLog::factory()->daysAgo($retention + 1)->for($this->user, 'authenticatable')->create();
|
||||||
|
|
||||||
|
$this->artisan('2fauth:purge-log');
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('auth_logs', [
|
||||||
|
'id' => $log->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_purgeLog_deletes_logout_only_records_older_than_retention_time()
|
||||||
|
{
|
||||||
|
$retention = 180;
|
||||||
|
config(['2fauth.config.authLogRetentionTime' => $retention]);
|
||||||
|
$log = AuthLog::factory()->logoutOnly()->for($this->user, 'authenticatable')->create();
|
||||||
|
|
||||||
|
$this->travelTo(Carbon::now()->addDays($retention + 1));
|
||||||
|
$this->artisan('2fauth:purge-log');
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('auth_logs', [
|
||||||
|
'id' => $log->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_purgeLog_does_not_delete_records_younger_than_retention_time()
|
||||||
|
{
|
||||||
|
$retention = 180;
|
||||||
|
config(['2fauth.config.authLogRetentionTime' => $retention]);
|
||||||
|
$log = AuthLog::factory()->daysAgo($retention - 1)->for($this->user, 'authenticatable')->create();
|
||||||
|
|
||||||
|
$this->artisan('2fauth:purge-log');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('auth_logs', [
|
||||||
|
'id' => $log->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
#[DataProvider('provideInvalidConfig')]
|
||||||
|
public function test_purgeLog_with_invalid_config_defaults_to_one_year($config)
|
||||||
|
{
|
||||||
|
config(['2fauth.config.authLogRetentionTime' => $config]);
|
||||||
|
$oneYearOldLog = AuthLog::factory()->daysAgo(366)->for($this->user, 'authenticatable')->create();
|
||||||
|
$sixMonthsOldLog = AuthLog::factory()->daysAgo(364)->for($this->user, 'authenticatable')->create();
|
||||||
|
|
||||||
|
$this->artisan('2fauth:purge-log');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('auth_logs', [
|
||||||
|
'id' => $sixMonthsOldLog->id
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseMissing('auth_logs', [
|
||||||
|
'id' => $oneYearOldLog->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide invalid config for validation test
|
||||||
|
*/
|
||||||
|
public static function provideInvalidConfig() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'NULL' => [
|
||||||
|
null
|
||||||
|
],
|
||||||
|
'EMPTY' => [
|
||||||
|
''
|
||||||
|
],
|
||||||
|
'STRING' => [
|
||||||
|
'ljhkjh'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user