fix(firewall): account for authentication failures exactly once
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -15,12 +15,14 @@ class FirewallLogObjectTest extends TestCase
|
||||
public function testRuleContextSerialization(): void
|
||||
{
|
||||
$log = (new FirewallLogObject())
|
||||
->setEventId('event-123')
|
||||
->setRuleId('rule-123')
|
||||
->setRuleScope(FirewallRuleObject::SCOPE_SYSTEM);
|
||||
|
||||
$restored = (new FirewallLogObject())->jsonDeserialize($log->jsonSerialize());
|
||||
|
||||
self::assertSame('rule-123', $restored->getRuleId());
|
||||
self::assertSame('event-123', $restored->getEventId());
|
||||
self::assertSame(FirewallRuleObject::SCOPE_SYSTEM, $restored->getRuleScope());
|
||||
}
|
||||
|
||||
|
||||
@@ -28,12 +28,13 @@ final class CoreModuleTest extends TestCase
|
||||
$module->boot();
|
||||
$definitions = $registry->definitions();
|
||||
|
||||
self::assertCount(10, $definitions);
|
||||
self::assertCount(9, $definitions);
|
||||
self::assertSame(['core'], array_values(array_unique(array_column($definitions, 'module'))));
|
||||
self::assertSame(
|
||||
FirewallService::class,
|
||||
$registry->listeners(SecurityEvent::AUTH_FAILURE, DeliveryMode::Immediate)[0]->service,
|
||||
);
|
||||
self::assertSame([], $registry->listeners(SecurityEvent::AUTH_FAILURE, DeliveryMode::Deferred));
|
||||
foreach ([
|
||||
SecurityEvent::RATE_LIMIT_EXCEEDED,
|
||||
SecurityEvent::SUSPICIOUS_ACTIVITY,
|
||||
|
||||
@@ -339,6 +339,7 @@ class FirewallServiceTest extends TestCase
|
||||
#[TestDox('Typed tenant firewall settings drive brute-force thresholds')]
|
||||
public function testFirewallConfiguration(): void
|
||||
{
|
||||
$this->store->method('createLogOnce')->willReturn(true);
|
||||
$this->currentConfiguration = (new TenantConfiguration())->jsonDeserialize([
|
||||
'firewall' => [
|
||||
'enabled' => true,
|
||||
@@ -364,6 +365,7 @@ class FirewallServiceTest extends TestCase
|
||||
#[TestDox('Unsafe numeric firewall settings fall back to safe defaults')]
|
||||
public function testConfigurationBounds(): void
|
||||
{
|
||||
$this->store->method('createLogOnce')->willReturn(true);
|
||||
$this->currentConfiguration = (new TenantConfiguration())->jsonDeserialize([
|
||||
'firewall' => [
|
||||
'maxAuthFailures' => 0,
|
||||
@@ -384,11 +386,12 @@ class FirewallServiceTest extends TestCase
|
||||
#[TestDox('Automatic blocks retain the tenant carried by the authentication event')]
|
||||
public function testAutomaticBlockTenant(): void
|
||||
{
|
||||
$this->store->method('createLogOnce')->willReturn(true);
|
||||
$this->currentTenant = 'tenant-context';
|
||||
$this->store->expects($this->once())
|
||||
->method('countRecentFailures')
|
||||
->with('tenant-event', '203.0.113.10', 300)
|
||||
->willReturn(4);
|
||||
->willReturn(5);
|
||||
$this->store->expects($this->once())
|
||||
->method('findExactIpRule')
|
||||
->with(
|
||||
@@ -431,6 +434,7 @@ class FirewallServiceTest extends TestCase
|
||||
#[TestDox('Authentication events without a tenant use the current tenant')]
|
||||
public function testAutomaticBlockTenantFallback(): void
|
||||
{
|
||||
$this->store->method('createLogOnce')->willReturn(true);
|
||||
$this->store->expects($this->once())
|
||||
->method('countRecentFailures')
|
||||
->with('tenant-a', '203.0.113.10', 300)
|
||||
@@ -453,6 +457,25 @@ class FirewallServiceTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
#[TestDox('Repeated delivery of one authentication event is counted once')]
|
||||
public function testAuthenticationFailureIdempotency(): void
|
||||
{
|
||||
$this->store->expects($this->exactly(2))
|
||||
->method('createLogOnce')
|
||||
->willReturnOnConsecutiveCalls(true, false);
|
||||
$this->store->expects($this->once())
|
||||
->method('countRecentFailures')
|
||||
->with('tenant-a', '203.0.113.10', 300)
|
||||
->willReturn(1);
|
||||
$event = \KTXF\Event\SecurityEvent::authFailure('203.0.113.10');
|
||||
$eventId = $event->getEventId();
|
||||
|
||||
$this->service->handleAuthFailure($event);
|
||||
$this->service->handleAuthFailure($event);
|
||||
|
||||
self::assertSame($eventId, $event->getEventId());
|
||||
}
|
||||
|
||||
private function rule(
|
||||
string $id,
|
||||
string $scope,
|
||||
|
||||
Reference in New Issue
Block a user