diff --git a/src/Firewall.php b/src/Firewall.php index bc57b8a..59681d1 100644 --- a/src/Firewall.php +++ b/src/Firewall.php @@ -80,7 +80,12 @@ public function getLastMatchedRule(): ?Rule } /** - * Evaluate the registered rules and return true when the request should be allowed. + * Evaluate registered rules in order against populated attributes. + * + * Sets the matched rule via getLastMatchedRule() when a rule's conditions + * match. Returns whether that rule's action allows the request to continue + * (bypass/rateLimit) or should be blocked (deny/challenge/redirect). + * Returns false when no rule matches. */ public function verify(): bool { diff --git a/src/Rule.php b/src/Rule.php index 7df6f2b..4cc6370 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -15,6 +15,8 @@ abstract class Rule */ protected array $conditions = []; + private ?string $id = null; + /** * @param array> $conditions */ @@ -34,6 +36,18 @@ static function (Condition|array $condition): Condition { abstract public function getAction(): string; + public function setId(string $id): self + { + $this->id = $id; + + return $this; + } + + public function getId(): ?string + { + return $this->id; + } + /** * @return array */ diff --git a/tests/FirewallTest.php b/tests/FirewallTest.php index d5a394c..5dc166c 100644 --- a/tests/FirewallTest.php +++ b/tests/FirewallTest.php @@ -11,6 +11,35 @@ class FirewallTest extends TestCase { + public function testVerifyUsesPopulatedRequestAttributesAndExposesMatchedRule(): void + { + $firewall = new Firewall(); + $firewall->setAttributes([ + 'requestIP' => '127.0.0.1', + 'requestPath' => '/v1/locale', + 'requestCountry' => 'IL', + ]); + + $deny = new Deny([ + Condition::equal('ip', ['127.0.0.1']), + Condition::contains('path', ['/v1']), + Condition::equal('country', ['IL']), + ]); + + $firewall->addRule($deny); + + $this->assertFalse($firewall->verify()); + $this->assertSame($deny, $firewall->getLastMatchedRule()); + + $firewall->clearRules(); + $firewall->addRule(new Deny([ + Condition::equal('country', ['US']), + ])); + + $this->assertFalse($firewall->verify()); + $this->assertNull($firewall->getLastMatchedRule()); + } + public function testRuleOrder(): void { $firewall = new Firewall(); @@ -63,4 +92,18 @@ public function testRateLimitMetadata(): void $this->assertSame(2, $matched->getLimit()); $this->assertSame(60, $matched->getInterval()); } + + public function testRuleIdentifierRoundTrip(): void + { + $rule = (new Deny([ + Condition::equal('ip', ['127.0.0.1']), + ]))->setId('rule_abc'); + + $firewall = new Firewall(); + $firewall->setAttribute('requestIP', '127.0.0.1'); + $firewall->addRule($rule); + + $this->assertFalse($firewall->verify()); + $this->assertSame('rule_abc', $firewall->getLastMatchedRule()?->getId()); + } }