From 2494cef02f59423d37cf1af15b2d7b20904d97eb Mon Sep 17 00:00:00 2001 From: Sebastian Krupinski Date: Wed, 5 Aug 2026 23:51:40 -0400 Subject: [PATCH] refactor(security): type firewall rule lifecycle events Signed-off-by: Sebastian Krupinski --- core/lib/Module/Module.php | 15 +- .../Event/FirewallRuleCreatedEvent.php | 9 + .../Event/FirewallRuleDisabledEvent.php | 9 + .../Event/FirewallRuleEnabledEvent.php | 9 + core/lib/Security/Event/FirewallRuleEvent.php | 158 ++++++++++++++++++ .../Event/FirewallRuleExtendedEvent.php | 9 + .../Event/FirewallRuleRemovedEvent.php | 9 + core/lib/Security/Event/SecurityEvent.php | 5 - core/lib/Service/FirewallRuleManager.php | 44 +++-- core/lib/Service/FirewallService.php | 25 +-- .../php/Unit/Event/FirewallRuleEventTest.php | 81 +++++++++ tests/php/Unit/Event/SecurityEventTest.php | 2 +- tests/php/Unit/Module/CoreModuleTest.php | 15 +- .../Unit/Service/FirewallRuleManagerTest.php | 33 ++-- .../Unit/Service/FirewallRuleServicesTest.php | 8 +- .../php/Unit/Service/FirewallServiceTest.php | 25 +-- 16 files changed, 383 insertions(+), 73 deletions(-) create mode 100644 core/lib/Security/Event/FirewallRuleCreatedEvent.php create mode 100644 core/lib/Security/Event/FirewallRuleDisabledEvent.php create mode 100644 core/lib/Security/Event/FirewallRuleEnabledEvent.php create mode 100644 core/lib/Security/Event/FirewallRuleEvent.php create mode 100644 core/lib/Security/Event/FirewallRuleExtendedEvent.php create mode 100644 core/lib/Security/Event/FirewallRuleRemovedEvent.php create mode 100644 tests/php/Unit/Event/FirewallRuleEventTest.php diff --git a/core/lib/Module/Module.php b/core/lib/Module/Module.php index 6234c08..45a5633 100644 --- a/core/lib/Module/Module.php +++ b/core/lib/Module/Module.php @@ -15,6 +15,11 @@ use KTXC\Security\Event\AccessDeniedEvent; use KTXC\Security\Event\AuthenticationFailedEvent; use KTXC\Security\Event\AuthenticationSucceededEvent; use KTXC\Security\Event\BruteForceDetectedEvent; +use KTXC\Security\Event\FirewallRuleCreatedEvent; +use KTXC\Security\Event\FirewallRuleDisabledEvent; +use KTXC\Security\Event\FirewallRuleEnabledEvent; +use KTXC\Security\Event\FirewallRuleExtendedEvent; +use KTXC\Security\Event\FirewallRuleRemovedEvent; use KTXC\Security\Event\RateLimitExceededEvent; use KTXC\Security\Event\SuspiciousActivityEvent; use KTXC\Security\Event\SecurityEvent; @@ -53,11 +58,11 @@ class Module extends ModuleInstanceAbstract implements ModuleConsoleInterface, M BruteForceDetectedEvent::class, RateLimitExceededEvent::class, SuspiciousActivityEvent::class, - SecurityEvent::FIREWALL_RULE_CREATED, - SecurityEvent::FIREWALL_RULE_EXTENDED, - SecurityEvent::FIREWALL_RULE_ENABLED, - SecurityEvent::FIREWALL_RULE_DISABLED, - SecurityEvent::FIREWALL_RULE_REMOVED, + FirewallRuleCreatedEvent::class, + FirewallRuleExtendedEvent::class, + FirewallRuleEnabledEvent::class, + FirewallRuleDisabledEvent::class, + FirewallRuleRemovedEvent::class, SecurityEvent::FIREWALL_SETTINGS_UPDATED, ] as $event) { $this->events->listen( diff --git a/core/lib/Security/Event/FirewallRuleCreatedEvent.php b/core/lib/Security/Event/FirewallRuleCreatedEvent.php new file mode 100644 index 0000000..efac612 --- /dev/null +++ b/core/lib/Security/Event/FirewallRuleCreatedEvent.php @@ -0,0 +1,9 @@ + $ruleScope, + 'type' => $ruleType, + 'action' => $ruleAction, + 'value' => $ruleValue, + 'origin' => $origin, + ] as $field => $value) { + if ($value === '') { + throw new \InvalidArgumentException("Firewall rule events require a rule {$field}."); + } + } + foreach ([ + 'scope' => $ruleScope, + 'type' => $ruleType, + 'action' => $ruleAction, + 'value' => $ruleValue, + 'origin' => $origin, + ] as $field => $value) { + if ($value === '') { + throw new \InvalidArgumentException("Firewall rule events require a rule {$field}."); + } + } + + parent::__construct( + static::class, + [ + 'ruleId' => $ruleId, + 'ruleScope' => $ruleScope, + 'ruleType' => $ruleType, + 'ruleAction' => $ruleAction, + 'ruleValue' => $ruleValue, + 'reason' => $reason, + 'origin' => $origin, + 'expiresAt' => $expiresAt, + ...$details, + ], + $tenantId, + $identityId, + ); + } + + public static function fromRule( + FirewallRuleObject $rule, + ?string $actorId = null, + array $change = [], + ): static { + $metadata = $rule->getMetadata() ?? []; + $details = [...$metadata, ...$change]; + foreach ([ + 'ruleId', + 'ruleScope', + 'ruleType', + 'ruleAction', + 'ruleValue', + 'reason', + 'origin', + 'expiresAt', + ] as $reservedKey) { + unset($details[$reservedKey]); + } + + return new static( + ruleId: (string) $rule->getId(), + ruleScope: (string) $rule->getScope(), + ruleType: (string) $rule->getType(), + ruleAction: (string) $rule->getAction(), + ruleValue: (string) $rule->getValue(), + reason: $rule->getReason(), + origin: (string) ($metadata['origin'] ?? 'manual'), + expiresAt: $rule->getExpiresAt()?->format(\DateTimeInterface::ATOM), + details: $details, + tenantId: $rule->getTenantId(), + identityId: $actorId ?? $rule->getCreatedBy(), + ); + } + + public function getRuleId(): string + { + return $this->ruleId; + } + + public function getRuleScope(): string + { + return $this->ruleScope; + } + + public function getRuleType(): string + { + return $this->ruleType; + } + + public function getRuleAction(): string + { + return $this->ruleAction; + } + + public function getRuleValue(): string + { + return $this->ruleValue; + } + + public function getOrigin(): string + { + return $this->origin; + } + + public function getExpiresAt(): ?string + { + return $this->expiresAt; + } + + public function getDetails(): array + { + return $this->details; + } + + public function getUserId(): ?string + { + return null; + } + + public function getReason(): ?string + { + return $this->reason; + } + + public function getSeverity(): int + { + return SecurityEvent::SEVERITY_INFO; + } +} diff --git a/core/lib/Security/Event/FirewallRuleExtendedEvent.php b/core/lib/Security/Event/FirewallRuleExtendedEvent.php new file mode 100644 index 0000000..c19f1c1 --- /dev/null +++ b/core/lib/Security/Event/FirewallRuleExtendedEvent.php @@ -0,0 +1,9 @@ +store->depositRule($rule); $this->cache->invalidate(); $this->publishLifecycleEvent( - SecurityEvent::FIREWALL_RULE_DISABLED, + FirewallRuleDisabledEvent::class, $rule, $actorId, ['changeReason' => $reason, 'changeOrigin' => self::ORIGIN_MANUAL] @@ -321,7 +327,7 @@ final class FirewallRuleManager $this->store->depositRule($rule); $this->cache->invalidate(); $this->publishLifecycleEvent( - SecurityEvent::FIREWALL_RULE_ENABLED, + FirewallRuleEnabledEvent::class, $rule, $actorId, ['changeReason' => $reason, 'changeOrigin' => self::ORIGIN_MANUAL] @@ -365,7 +371,7 @@ final class FirewallRuleManager $this->store->depositRule($rule); $this->cache->invalidate(); $this->publishLifecycleEvent( - SecurityEvent::FIREWALL_RULE_EXTENDED, + FirewallRuleExtendedEvent::class, $rule, $actorId, [ @@ -392,7 +398,7 @@ final class FirewallRuleManager $this->store->destroyRule($rule); $this->cache->invalidate(); $this->publishLifecycleEvent( - SecurityEvent::FIREWALL_RULE_REMOVED, + FirewallRuleRemovedEvent::class, $rule, $actorId, ['changeReason' => $reason, 'changeOrigin' => self::ORIGIN_MANUAL] @@ -447,9 +453,10 @@ final class FirewallRuleManager } $rule->setMetadata($metadata); - $this->store->depositRule($rule); + $rule = $this->store->depositRule($rule) + ?? throw new \RuntimeException('Failed to persist firewall rule.'); $this->cache->invalidate(); - $this->publishLifecycleEvent(SecurityEvent::FIREWALL_RULE_CREATED, $rule); + $this->publishLifecycleEvent(FirewallRuleCreatedEvent::class, $rule); return $rule; } @@ -486,7 +493,7 @@ final class FirewallRuleManager $this->store->depositRule($rule); $this->cache->invalidate(); - $this->publishLifecycleEvent(SecurityEvent::FIREWALL_RULE_EXTENDED, $rule); + $this->publishLifecycleEvent(FirewallRuleExtendedEvent::class, $rule); return $rule; } @@ -514,30 +521,17 @@ final class FirewallRuleManager $this->events->dispatch($event); } + /** + * @param class-string $eventClass + */ private function publishLifecycleEvent( - string $name, + string $eventClass, FirewallRuleObject $rule, ?string $actorId = null, array $change = [] ): void { - $event = new SecurityEvent( - $name, - [ - 'ruleId' => $rule->getId(), - 'ruleScope' => $rule->getScope(), - 'ruleType' => $rule->getType(), - 'ruleAction' => $rule->getAction(), - 'ruleValue' => $rule->getValue(), - 'reason' => $rule->getReason(), - 'origin' => $rule->getMetadata()['origin'] ?? self::ORIGIN_MANUAL, - 'expiresAt' => $rule->getExpiresAt()?->format(\DateTimeInterface::ATOM), - ...($rule->getMetadata() ?? []), - ...$change, - ], - tenantId: $rule->getTenantId(), - identityId: $actorId ?? $rule->getCreatedBy(), - ); + $event = $eventClass::fromRule($rule, $actorId, $change); $this->events->dispatch($event); } } diff --git a/core/lib/Service/FirewallService.php b/core/lib/Service/FirewallService.php index 24c2ffa..de71afd 100644 --- a/core/lib/Service/FirewallService.php +++ b/core/lib/Service/FirewallService.php @@ -14,6 +14,11 @@ use KTXC\Security\Event\AccessDeniedEvent; use KTXC\Security\Event\AuthenticationFailedEvent; use KTXC\Security\Event\AuthenticationSucceededEvent; use KTXC\Security\Event\BruteForceDetectedEvent; +use KTXC\Security\Event\FirewallRuleCreatedEvent; +use KTXC\Security\Event\FirewallRuleDisabledEvent; +use KTXC\Security\Event\FirewallRuleEnabledEvent; +use KTXC\Security\Event\FirewallRuleExtendedEvent; +use KTXC\Security\Event\FirewallRuleRemovedEvent; use KTXC\Security\Event\RateLimitExceededEvent; use KTXC\Security\Event\SuspiciousActivityEvent; use KTXC\Security\Event\SecurityEvent; @@ -289,11 +294,11 @@ class FirewallService RateLimitExceededEvent::class => FirewallLogObject::EVENT_RATE_LIMIT, AccessDeniedEvent::class => FirewallLogObject::EVENT_RULE_MATCH, SuspiciousActivityEvent::class => FirewallLogObject::EVENT_SUSPICIOUS, - SecurityEvent::FIREWALL_RULE_CREATED => FirewallLogObject::EVENT_RULE_CREATED, - SecurityEvent::FIREWALL_RULE_EXTENDED => FirewallLogObject::EVENT_RULE_EXTENDED, - SecurityEvent::FIREWALL_RULE_ENABLED => FirewallLogObject::EVENT_RULE_ENABLED, - SecurityEvent::FIREWALL_RULE_DISABLED => FirewallLogObject::EVENT_RULE_DISABLED, - SecurityEvent::FIREWALL_RULE_REMOVED => FirewallLogObject::EVENT_RULE_REMOVED, + FirewallRuleCreatedEvent::class => FirewallLogObject::EVENT_RULE_CREATED, + FirewallRuleExtendedEvent::class => FirewallLogObject::EVENT_RULE_EXTENDED, + FirewallRuleEnabledEvent::class => FirewallLogObject::EVENT_RULE_ENABLED, + FirewallRuleDisabledEvent::class => FirewallLogObject::EVENT_RULE_DISABLED, + FirewallRuleRemovedEvent::class => FirewallLogObject::EVENT_RULE_REMOVED, SecurityEvent::FIREWALL_SETTINGS_UPDATED => FirewallLogObject::EVENT_SETTINGS_UPDATED, default => FirewallLogObject::EVENT_ACCESS_CHECK, }; @@ -307,11 +312,11 @@ class FirewallService return match ($event->getName()) { AuthenticationSucceededEvent::class, SecurityEvent::ACCESS_GRANTED => FirewallLogObject::RESULT_ALLOWED, - SecurityEvent::FIREWALL_RULE_CREATED, - SecurityEvent::FIREWALL_RULE_EXTENDED, - SecurityEvent::FIREWALL_RULE_ENABLED, - SecurityEvent::FIREWALL_RULE_DISABLED, - SecurityEvent::FIREWALL_RULE_REMOVED, + FirewallRuleCreatedEvent::class, + FirewallRuleExtendedEvent::class, + FirewallRuleEnabledEvent::class, + FirewallRuleDisabledEvent::class, + FirewallRuleRemovedEvent::class, SecurityEvent::FIREWALL_SETTINGS_UPDATED => FirewallLogObject::RESULT_RECORDED, default => FirewallLogObject::RESULT_BLOCKED, }; diff --git a/tests/php/Unit/Event/FirewallRuleEventTest.php b/tests/php/Unit/Event/FirewallRuleEventTest.php new file mode 100644 index 0000000..62fcead --- /dev/null +++ b/tests/php/Unit/Event/FirewallRuleEventTest.php @@ -0,0 +1,81 @@ +setId('rule-a') + ->setScope(FirewallRuleObject::SCOPE_TENANT) + ->setTenantId('tenant-a') + ->setType(FirewallRuleObject::TYPE_IP) + ->setAction(FirewallRuleObject::ACTION_BLOCK) + ->setValue('203.0.113.10') + ->setReason('Repeated abuse') + ->setCreatedBy('creator-a') + ->setExpiresAt($expiresAt) + ->setMetadata(['origin' => 'automatic', 'failureCount' => 5]); + + $event = FirewallRuleExtendedEvent::fromRule( + $rule, + 'operator-a', + ['changeReason' => 'Continue monitoring'], + ); + $rule->setReason('Mutated after publication'); + + self::assertSame(FirewallRuleExtendedEvent::class, $event->getName()); + self::assertSame('rule-a', $event->getRuleId()); + self::assertSame(FirewallRuleObject::SCOPE_TENANT, $event->getRuleScope()); + self::assertSame(FirewallRuleObject::TYPE_IP, $event->getRuleType()); + self::assertSame(FirewallRuleObject::ACTION_BLOCK, $event->getRuleAction()); + self::assertSame('203.0.113.10', $event->getRuleValue()); + self::assertSame('Repeated abuse', $event->getReason()); + self::assertSame('automatic', $event->getOrigin()); + self::assertSame($expiresAt->format(\DateTimeInterface::ATOM), $event->getExpiresAt()); + self::assertSame('tenant-a', $event->getTenantId()); + self::assertSame('operator-a', $event->getIdentityId()); + self::assertSame(5, $event->get('failureCount')); + self::assertSame('Continue monitoring', $event->get('changeReason')); + self::assertSame(SecurityEvent::SEVERITY_INFO, $event->getSeverity()); + } + + #[Test] + #[TestDox('Each firewall rule lifecycle operation has a dedicated event key')] + public function exposesDedicatedLifecycleKeys(): void + { + $rule = (new FirewallRuleObject()) + ->setId('rule-a') + ->setScope(FirewallRuleObject::SCOPE_SYSTEM) + ->setType(FirewallRuleObject::TYPE_IP) + ->setAction(FirewallRuleObject::ACTION_BLOCK) + ->setValue('203.0.113.10'); + + foreach ([ + FirewallRuleCreatedEvent::class, + FirewallRuleExtendedEvent::class, + FirewallRuleEnabledEvent::class, + FirewallRuleDisabledEvent::class, + FirewallRuleRemovedEvent::class, + ] as $eventClass) { + self::assertSame($eventClass, $eventClass::fromRule($rule)->getName()); + } + } +} diff --git a/tests/php/Unit/Event/SecurityEventTest.php b/tests/php/Unit/Event/SecurityEventTest.php index 48650b0..23789d0 100644 --- a/tests/php/Unit/Event/SecurityEventTest.php +++ b/tests/php/Unit/Event/SecurityEventTest.php @@ -25,7 +25,7 @@ final class SecurityEventTest extends TestCase ); self::assertSame( SecurityEvent::SEVERITY_INFO, - (new SecurityEvent(SecurityEvent::FIREWALL_RULE_CREATED))->getSeverity(), + (new SecurityEvent(SecurityEvent::IP_ALLOWED))->getSeverity(), ); } diff --git a/tests/php/Unit/Module/CoreModuleTest.php b/tests/php/Unit/Module/CoreModuleTest.php index 57c442c..79ce4e0 100644 --- a/tests/php/Unit/Module/CoreModuleTest.php +++ b/tests/php/Unit/Module/CoreModuleTest.php @@ -20,6 +20,11 @@ use KTXC\Security\Event\AccessDeniedEvent; use KTXC\Security\Event\AuthenticationFailedEvent; use KTXC\Security\Event\AuthenticationSucceededEvent; use KTXC\Security\Event\BruteForceDetectedEvent; +use KTXC\Security\Event\FirewallRuleCreatedEvent; +use KTXC\Security\Event\FirewallRuleDisabledEvent; +use KTXC\Security\Event\FirewallRuleEnabledEvent; +use KTXC\Security\Event\FirewallRuleExtendedEvent; +use KTXC\Security\Event\FirewallRuleRemovedEvent; use KTXC\Security\Event\RateLimitExceededEvent; use KTXC\Security\Event\SuspiciousActivityEvent; use KTXC\Security\Event\SecurityEvent; @@ -53,11 +58,11 @@ final class CoreModuleTest extends TestCase BruteForceDetectedEvent::class, RateLimitExceededEvent::class, SuspiciousActivityEvent::class, - SecurityEvent::FIREWALL_RULE_CREATED, - SecurityEvent::FIREWALL_RULE_EXTENDED, - SecurityEvent::FIREWALL_RULE_DISABLED, - SecurityEvent::FIREWALL_RULE_ENABLED, - SecurityEvent::FIREWALL_RULE_REMOVED, + FirewallRuleCreatedEvent::class, + FirewallRuleExtendedEvent::class, + FirewallRuleDisabledEvent::class, + FirewallRuleEnabledEvent::class, + FirewallRuleRemovedEvent::class, SecurityEvent::FIREWALL_SETTINGS_UPDATED, ] as $event) { $listeners = $registry->listeners($event, DeliveryMode::Deferred); diff --git a/tests/php/Unit/Service/FirewallRuleManagerTest.php b/tests/php/Unit/Service/FirewallRuleManagerTest.php index 641bb56..efb3370 100644 --- a/tests/php/Unit/Service/FirewallRuleManagerTest.php +++ b/tests/php/Unit/Service/FirewallRuleManagerTest.php @@ -11,7 +11,11 @@ use KTXC\Service\FirewallRuleManager; use KTXC\Service\FirewallRuleScope; use KTXC\Stores\FirewallStore; use KTXF\Event\EventDispatcherInterface; -use KTXC\Security\Event\SecurityEvent; +use KTXC\Security\Event\FirewallRuleCreatedEvent; +use KTXC\Security\Event\FirewallRuleDisabledEvent; +use KTXC\Security\Event\FirewallRuleEnabledEvent; +use KTXC\Security\Event\FirewallRuleExtendedEvent; +use KTXC\Security\Event\FirewallRuleRemovedEvent; use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\MockObject\MockObject; @@ -41,7 +45,10 @@ class FirewallRuleManagerTest extends TestCase $this->store->method('findExactIpRule')->willReturn(null); $this->store->expects($this->exactly(2)) ->method('depositRule') - ->willReturnArgument(0); + ->willReturnCallback(static function (FirewallRuleObject $rule): FirewallRuleObject { + static $sequence = 0; + return $rule->setId('rule-' . ++$sequence); + }); $tenant = $this->manager->blockIp( FirewallRuleScope::tenant('tenant-a'), '203.0.113.10', null, 'admin-a' @@ -135,7 +142,11 @@ class FirewallRuleManagerTest extends TestCase public function testConfirmedCurrentIpBlock(): void { $this->store->method('findExactIpRule')->willReturn(null); - $this->store->expects(self::once())->method('depositRule')->willReturnArgument(0); + $this->store->expects(self::once()) + ->method('depositRule') + ->willReturnCallback(static fn(FirewallRuleObject $rule): FirewallRuleObject => + $rule->setId('rule-confirmed') + ); $rule = $this->manager->createManualRule( FirewallRuleScope::tenant('tenant-a'), @@ -249,7 +260,9 @@ class FirewallRuleManagerTest extends TestCase $cache = new FirewallRuleCache($this->store); $manager = new FirewallRuleManager($this->store, $cache, $this->events); $this->store->method('findExactIpRule')->willReturn(null); - $this->store->method('depositRule')->willReturnArgument(0); + $this->store->method('depositRule')->willReturnCallback( + static fn(FirewallRuleObject $rule): FirewallRuleObject => $rule->setId('rule-cache') + ); self::assertSame([], $cache->tenant('tenant-a')); $manager->blockIp(FirewallRuleScope::tenant('tenant-a'), '203.0.113.10', null, 'admin'); @@ -280,7 +293,7 @@ class FirewallRuleManagerTest extends TestCase 300 ); - $audit = $events[SecurityEvent::FIREWALL_RULE_CREATED]; + $audit = $events[FirewallRuleCreatedEvent::class]; self::assertSame('rule-123', $audit->get('ruleId')); self::assertSame(FirewallRuleObject::SCOPE_TENANT, $audit->get('ruleScope')); self::assertSame(FirewallRuleObject::TYPE_IP, $audit->get('ruleType')); @@ -329,10 +342,10 @@ class FirewallRuleManagerTest extends TestCase self::assertGreaterThan($originalExpiry, $extended->getExpiresAt()); self::assertCount(1, $extended->getMetadata()['extensions']); self::assertSame([ - SecurityEvent::FIREWALL_RULE_DISABLED, - SecurityEvent::FIREWALL_RULE_ENABLED, - SecurityEvent::FIREWALL_RULE_EXTENDED, - SecurityEvent::FIREWALL_RULE_REMOVED, + FirewallRuleDisabledEvent::class, + FirewallRuleEnabledEvent::class, + FirewallRuleExtendedEvent::class, + FirewallRuleRemovedEvent::class, ], array_map(static fn($event): string => $event->getName(), $audits)); self::assertSame( ['Investigation', 'Threat confirmed', 'Continue monitoring', 'Case closed'], @@ -401,7 +414,7 @@ class FirewallRuleManagerTest extends TestCase $this->events->expects(self::once()) ->method('dispatch') ->with(self::callback(static fn(\KTXF\Event\Event $event): bool => - $event->getName() === SecurityEvent::FIREWALL_RULE_EXTENDED + $event instanceof FirewallRuleExtendedEvent && $event->get('lastFailureCount') === 8 )); diff --git a/tests/php/Unit/Service/FirewallRuleServicesTest.php b/tests/php/Unit/Service/FirewallRuleServicesTest.php index 06d5051..a6042ea 100644 --- a/tests/php/Unit/Service/FirewallRuleServicesTest.php +++ b/tests/php/Unit/Service/FirewallRuleServicesTest.php @@ -102,7 +102,9 @@ class FirewallRuleServicesTest extends TestCase ->with(self::callback(static fn(FirewallRuleObject $rule): bool => $rule->isTenantScoped() && $rule->getTenantId() === 'tenant-a' )) - ->willReturnArgument(0); + ->willReturnCallback(static fn(FirewallRuleObject $rule): FirewallRuleObject => + $rule->setId('tenant-rule') + ); $this->tenantService()->blockIp('203.0.113.10'); } @@ -117,7 +119,9 @@ class FirewallRuleServicesTest extends TestCase ->with(self::callback(static fn(FirewallRuleObject $rule): bool => $rule->isSystemScoped() && $rule->getTenantId() === null )) - ->willReturnArgument(0); + ->willReturnCallback(static fn(FirewallRuleObject $rule): FirewallRuleObject => + $rule->setId('system-rule') + ); $this->systemService()->blockIp('203.0.113.10'); } diff --git a/tests/php/Unit/Service/FirewallServiceTest.php b/tests/php/Unit/Service/FirewallServiceTest.php index 19877bb..cc8da9d 100644 --- a/tests/php/Unit/Service/FirewallServiceTest.php +++ b/tests/php/Unit/Service/FirewallServiceTest.php @@ -14,6 +14,8 @@ use KTXC\Security\Event\AccessDeniedEvent; use KTXC\Security\Event\AuthenticationFailedEvent; use KTXC\Security\Event\AuthenticationSucceededEvent; use KTXC\Security\Event\BruteForceDetectedEvent; +use KTXC\Security\Event\FirewallRuleCreatedEvent; +use KTXC\Security\Event\FirewallRuleDisabledEvent; use KTXC\Security\Event\RateLimitExceededEvent; use KTXC\Security\Event\SuspiciousActivityEvent; use KTXC\Service\FirewallService; @@ -367,14 +369,15 @@ class FirewallServiceTest extends TestCase && $log->getIdentityId() === 'operator'; })) ->willReturnArgument(0); - $event = new \KTXC\Security\Event\SecurityEvent( - \KTXC\Security\Event\SecurityEvent::FIREWALL_RULE_DISABLED, - [ - 'ruleId' => 'rule-123', - 'ruleScope' => FirewallRuleObject::SCOPE_SYSTEM, - 'origin' => FirewallRuleManager::ORIGIN_MANUAL, - ], - identityId: 'operator', + $event = FirewallRuleDisabledEvent::fromRule( + (new FirewallRuleObject()) + ->setId('rule-123') + ->setScope(FirewallRuleObject::SCOPE_SYSTEM) + ->setType(FirewallRuleObject::TYPE_IP) + ->setAction(FirewallRuleObject::ACTION_BLOCK) + ->setValue('203.0.113.10') + ->setMetadata(['origin' => FirewallRuleManager::ORIGIN_MANUAL]), + 'operator', ); $this->service->logSecurityEvent($event); @@ -521,7 +524,9 @@ class FirewallServiceTest extends TestCase && $metadata['lastFailureCount'] === 5 && $metadata['blockDurationSeconds'] === 3600; })) - ->willReturnArgument(0); + ->willReturnCallback(static fn(FirewallRuleObject $rule): FirewallRuleObject => + $rule->setId('automatic-rule') + ); $publishedTenants = []; $lifecycleOrigin = null; @@ -537,7 +542,7 @@ class FirewallServiceTest extends TestCase if ($event instanceof BruteForceDetectedEvent) { $bruteForceEvent = $event; } - if ($event->getName() === \KTXC\Security\Event\SecurityEvent::FIREWALL_RULE_CREATED) { + if ($event instanceof FirewallRuleCreatedEvent) { $lifecycleOrigin = $event->get('origin'); } });