From 25c414a4d9555647f8f62fe29766dba8c88f92f3 Mon Sep 17 00:00:00 2001 From: "Nicholas K. Dionysopoulos" Date: Tue, 23 Jul 2024 20:02:54 +0300 Subject: [PATCH] ! Fixing a chicken and egg issue not allowing the update to proceed correctly Signed-off-by: Nicholas K. Dionysopoulos --- CHANGELOG | 4 ++ RELEASENOTES.md | 1 + src/Application/BootstrapUtilities.php | 19 ++++++-- src/Model/Loginfailures.php | 63 ++++++++++++++++++++++++-- src/Model/Task.php | 2 +- 5 files changed, 79 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 16d1a66e..61569316 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,8 @@ +Akeeba Panopticon 1.2.1 +================================================================================ +! Fixing a chicken and egg issue not allowing the update to proceed correctly + Akeeba Panopticon 1.2.0 ================================================================================ + WordPress support [gh-38] diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3a50857c..b63a4041 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -33,6 +33,7 @@ Development of Akeeba Panopticon takes place _in public_. You can see what we're ## 📋 CHANGELOG +* ‼️ Fixing a chicken and egg issue not allowing the update to proceed correctly * ✨ WordPress support [gh-38] * ✨ Much improved Docker support [gh-697] * ✨ Translatable dates diff --git a/src/Application/BootstrapUtilities.php b/src/Application/BootstrapUtilities.php index 482a10f9..0d7f59e5 100644 --- a/src/Application/BootstrapUtilities.php +++ b/src/Application/BootstrapUtilities.php @@ -513,14 +513,25 @@ public static function evaluateIPBlocking() /** @var Loginfailures $loginfailures */ $loginfailures = Factory::getContainer()->mvcFactory->makeModel('Loginfailures'); - if ($loginfailures->isIPBlocked()) + try + { + $isIPBlocked = $loginfailures->isIPBlocked(); + } + catch (\Exception $e) { + return; + } + + if (!$isIPBlocked) + { + return; + } + header('HTTP/1.0 403 Forbidden'); - @include APATH_THEMES . '/system/forbidden.html.php'; + @include APATH_THEMES . '/system/forbidden.html.php'; - exit(); - } + exit(); } /** diff --git a/src/Model/Loginfailures.php b/src/Model/Loginfailures.php index 80b95d1e..671bfd3c 100644 --- a/src/Model/Loginfailures.php +++ b/src/Model/Loginfailures.php @@ -22,6 +22,8 @@ */ class Loginfailures extends Model { + static ?bool $isAvailable = null; + /** * Log a failed login attempt. * @@ -33,6 +35,11 @@ class Loginfailures extends Model */ public function logFailure(bool $autoBlock = true): void { + if (!$this->isAvailable()) + { + return; + } + // Is the feature enabled? if (!((bool) $this->getContainer()->appConfig->get('login_failure_enable', 1))) { @@ -115,6 +122,11 @@ public function logFailure(bool $autoBlock = true): void */ public function cleanupOldFailures(): void { + if (!$this->isAvailable()) + { + return; + } + // Is the feature enabled? if (!((bool) $this->getContainer()->appConfig->get('login_failure_enable', 1))) { @@ -142,7 +154,8 @@ public function cleanupOldFailures(): void ->where( [ $db->quoteName('ip') . ' = INET6_ATON(' . $db->quote($ip) . ')', - $db->quoteName('mark') . ' < DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL ' . intval($maxSeconds) . ' SECOND)', + $db->quoteName('mark') . ' < DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL ' . intval($maxSeconds) + . ' SECOND)', ] ); @@ -180,6 +193,11 @@ public function cleanupOldFailures(): void */ public function isIPBlocked(): bool { + if (!$this->isAvailable()) + { + return false; + } + // Is the feature enabled? if (!((bool) $this->getContainer()->appConfig->get('login_failure_enable', 1))) { @@ -278,6 +296,11 @@ public function isIPBlocked(): bool */ public function blockIp(): void { + if (!$this->isAvailable()) + { + return; + } + // Is the feature enabled? if (!((bool) $this->getContainer()->appConfig->get('login_failure_enable', 1))) { @@ -326,10 +349,12 @@ public function blockIp(): void // Insert a record $insertQuery = $db->getQuery(true) ->insert($db->quoteName('#__login_lockouts')) - ->columns([ - $db->quoteName('ip'), - $db->quoteName('until') - ]) + ->columns( + [ + $db->quoteName('ip'), + $db->quoteName('until'), + ] + ) ->values( 'INET6_ATON(' . $db->quote($ip) . '), DATE_ADD(CURRENT_TIMESTAMP(), INTERVAL ' . (int) $lockoutTime . ' SECOND)' @@ -362,6 +387,11 @@ public function blockIp(): void */ public function mustBeBlocked(): bool { + if (!$this->isAvailable()) + { + return false; + } + // Is the feature enabled? if (!((bool) $this->getContainer()->appConfig->get('login_failure_enable', 1))) { @@ -420,6 +450,29 @@ public function mustBeBlocked(): bool return $failuresInWindow >= $maxAllowedFailures; } + private function isAvailable(): bool + { + if (self::$isAvailable !== null) + { + return self::$isAvailable; + } + + try + { + $db = $this->container->db; + $query = 'SHOW TABLES LIKE ' . $db->quote('#__login_failures'); + $tables = $db->setQuery($query)->loadColumn(); + + self::$isAvailable = !empty($tables); + } + catch (\Throwable $e) + { + self::$isAvailable = false; + } + + return self::$isAvailable; + } + /** * Convert a MySQL binary IP address to a printable string (Network to Printable). * diff --git a/src/Model/Task.php b/src/Model/Task.php index 5c0db17a..7623877a 100644 --- a/src/Model/Task.php +++ b/src/Model/Task.php @@ -202,7 +202,7 @@ public function check(): self ? $this->cron_expression : new CronExpression($this->cron_expression); // Warning! The last execution time is ALWAYS stored in UTC - $relativeTime = ($this->container->dateFactory($this->last_execution ?: 'now', 'UTC'))->toW3C(); + $relativeTime = ($this->container->dateFactory($this->last_execution ?: 'now', 'UTC'))->format('Y-m-d\TH:i:sP', false, false); // The call to getNextRunDate must use our local timezone because the CRON expression is in local time $nextRun = $cron_expression->getNextRunDate($relativeTime, timeZone: $tz)->format(DATE_W3C);