diff --git a/src/Extension/Bfstop.php b/src/Extension/Bfstop.php index 20255c2..dd828ab 100644 --- a/src/Extension/Bfstop.php +++ b/src/Extension/Bfstop.php @@ -37,6 +37,7 @@ public static function getSubscribedEvents(): array 'onUserLoginFailure' => 'onUserLoginFailure', 'onUserLogin' => 'onUserLogin', 'onAfterInitialise' => 'onAfterInitialise', + 'onAfterRoute' => 'onAfterRoute', ); } @@ -512,6 +513,25 @@ public function onAfterInitialise($event) $this->mydb->saveParams($this->params); } } + } + + /** + * Enforcing the block happens after routing, not in onAfterInitialise: + * the exemptions below look at option/view/task, which with + * search-engine-friendly URLs (e.g. /index.php/component/users/reset) + * are only populated once the router has run. + */ + public function onAfterRoute($event) + { + if (!isset($this->mydb)) + { + // onAfterInitialise did not run (or bailed out early) + $this->init(); + } + if (!$this->isEnabledForCurrentOrigin()) + { + return; + } $ipaddress = IpHelper::getAddress($this->logger); if ($this->mydb->isIPOnAllowList($ipaddress)) { diff --git a/tests/Integration/BlockedRequestTest.php b/tests/Integration/BlockedRequestTest.php index a142dfb..55ffeed 100644 --- a/tests/Integration/BlockedRequestTest.php +++ b/tests/Integration/BlockedRequestTest.php @@ -56,10 +56,10 @@ private function block($ip = self::Ip, $minutesAgo = 0, $duration = 60) 'crdate' => self::minutesAgo($minutesAgo), 'duration' => $duration), 'id'); } - private function request($query = '', $ip = self::Ip) + private function request($query = '', $ip = self::Ip, $routed = true) { $command = escapeshellarg(PHP_BINARY).' '.escapeshellarg(__DIR__.'/fixtures/request.php').' '. - escapeshellarg($ip).' '.escapeshellarg($query).' 2>&1'; + escapeshellarg($ip).' '.escapeshellarg($query).($routed ? '' : ' --no-route').' 2>&1'; exec($command, $output, $exitCode); $output = implode("\n", $output); $this->assertSame(0, $exitCode, $output); @@ -90,6 +90,17 @@ public function testFullModeBlocksEverything() $this->assertNotBlocked('', '203.0.113.42'); } + public function testBlockIsEnforcedOnlyAfterRouting() + { + // with SEF URLs option/view are empty until the router ran, so the + // password recovery exemption can only be evaluated afterwards + $this->configure(); + $this->block(); + $output = $this->request('', self::Ip, false); + $this->assertStringContainsString('NOT BLOCKED', $output); + $this->assertBlocked(); + } + public function testBlockedMessageCanShowIp() { $this->configure(array('blockedMsgShowIP' => 1)); diff --git a/tests/Integration/fixtures/request.php b/tests/Integration/fixtures/request.php index 61a6afc..4581166 100644 --- a/tests/Integration/fixtures/request.php +++ b/tests/Integration/fixtures/request.php @@ -27,4 +27,9 @@ } PluginHelper::importPlugin('system', 'bfstop', true, $app->getDispatcher()); $app->getDispatcher()->dispatch('onAfterInitialise', new Event('onAfterInitialise', array())); +// blocking is enforced after routing (SEF URLs only yield option/view then) +if (!in_array('--no-route', $argv, true)) +{ + $app->getDispatcher()->dispatch('onAfterRoute', new Event('onAfterRoute', array())); +} echo "NOT BLOCKED\n";