4b9a675d0f
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 36s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 40s
Generic: Project CI / Tests (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
All Joomla element names, PHP classes, language files, folder structure, and manifest references renamed from mokosuite to mokosuiteclient. This repo is now the client-facing tracker for the MokoSuite platform.
216 lines
5.9 KiB
PHP
216 lines
5.9 KiB
PHP
<?php
|
|
/**
|
|
* @package MokoSuiteClient
|
|
* @subpackage com_mokosuiteclient
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
*/
|
|
|
|
namespace Moko\Component\MokoSuiteClient\Administrator\Model;
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
|
|
|
class WaflogModel extends BaseDatabaseModel
|
|
{
|
|
/**
|
|
* Get WAF log entries with filters and pagination.
|
|
*/
|
|
public function getLogs(array $filters = [], int $limit = 50, int $offset = 0): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$query = $db->getQuery(true)
|
|
->select('*')
|
|
->from($db->quoteName('#__mokosuiteclient_waf_log'));
|
|
|
|
if (!empty($filters['rule']))
|
|
{
|
|
$query->where($db->quoteName('rule') . ' = ' . $db->quote($filters['rule']));
|
|
}
|
|
|
|
if (!empty($filters['ip']))
|
|
{
|
|
$query->where($db->quoteName('ip') . ' LIKE ' . $db->quote('%' . $db->escape($filters['ip'], true) . '%'));
|
|
}
|
|
|
|
if (!empty($filters['search']))
|
|
{
|
|
$search = $db->quote('%' . $db->escape($filters['search'], true) . '%');
|
|
$query->where('(' . $db->quoteName('uri') . ' LIKE ' . $search
|
|
. ' OR ' . $db->quoteName('detail') . ' LIKE ' . $search
|
|
. ' OR ' . $db->quoteName('user_agent') . ' LIKE ' . $search . ')');
|
|
}
|
|
|
|
if (!empty($filters['date_from']))
|
|
{
|
|
$query->where($db->quoteName('created') . ' >= ' . $db->quote($filters['date_from'] . ' 00:00:00'));
|
|
}
|
|
|
|
if (!empty($filters['date_to']))
|
|
{
|
|
$query->where($db->quoteName('created') . ' <= ' . $db->quote($filters['date_to'] . ' 23:59:59'));
|
|
}
|
|
|
|
$query->order($db->quoteName('created') . ' DESC');
|
|
$query->setLimit($limit, $offset);
|
|
|
|
$db->setQuery($query);
|
|
|
|
return $db->loadObjectList() ?: [];
|
|
}
|
|
|
|
/**
|
|
* Get total count for pagination.
|
|
*/
|
|
public function getTotal(array $filters = []): int
|
|
{
|
|
$db = $this->getDatabase();
|
|
$query = $db->getQuery(true)
|
|
->select('COUNT(*)')
|
|
->from($db->quoteName('#__mokosuiteclient_waf_log'));
|
|
|
|
if (!empty($filters['rule']))
|
|
{
|
|
$query->where($db->quoteName('rule') . ' = ' . $db->quote($filters['rule']));
|
|
}
|
|
|
|
if (!empty($filters['ip']))
|
|
{
|
|
$query->where($db->quoteName('ip') . ' LIKE ' . $db->quote('%' . $db->escape($filters['ip'], true) . '%'));
|
|
}
|
|
|
|
$db->setQuery($query);
|
|
|
|
return (int) $db->loadResult();
|
|
}
|
|
|
|
/**
|
|
* Get block counts grouped by rule for the summary bar.
|
|
*/
|
|
public function getRuleCounts(): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select([$db->quoteName('rule'), 'COUNT(*) AS ' . $db->quoteName('cnt')])
|
|
->from($db->quoteName('#__mokosuiteclient_waf_log'))
|
|
->group($db->quoteName('rule'))
|
|
->order($db->quoteName('cnt') . ' DESC')
|
|
);
|
|
|
|
return $db->loadObjectList() ?: [];
|
|
}
|
|
|
|
/**
|
|
* Get top blocked IPs.
|
|
*/
|
|
public function getTopIps(int $limit = 10): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select([$db->quoteName('ip'), 'COUNT(*) AS ' . $db->quoteName('cnt'),
|
|
'MAX(' . $db->quoteName('created') . ') AS ' . $db->quoteName('last_seen')])
|
|
->from($db->quoteName('#__mokosuiteclient_waf_log'))
|
|
->group($db->quoteName('ip'))
|
|
->order($db->quoteName('cnt') . ' DESC')
|
|
->setLimit($limit)
|
|
);
|
|
|
|
return $db->loadObjectList() ?: [];
|
|
}
|
|
|
|
/**
|
|
* Get distinct rule names for the filter dropdown.
|
|
*/
|
|
public function getRuleNames(): array
|
|
{
|
|
$db = $this->getDatabase();
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->select('DISTINCT ' . $db->quoteName('rule'))
|
|
->from($db->quoteName('#__mokosuiteclient_waf_log'))
|
|
->order($db->quoteName('rule') . ' ASC')
|
|
);
|
|
|
|
return $db->loadColumn() ?: [];
|
|
}
|
|
|
|
/**
|
|
* Delete logs older than N days.
|
|
*/
|
|
public function purgeLogs(int $days): array
|
|
{
|
|
try
|
|
{
|
|
$db = $this->getDatabase();
|
|
$cutoff = Factory::getDate('-' . $days . ' days')->toSql();
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->delete($db->quoteName('#__mokosuiteclient_waf_log'))
|
|
->where($db->quoteName('created') . ' < ' . $db->quote($cutoff))
|
|
)->execute();
|
|
|
|
$count = $db->getAffectedRows();
|
|
|
|
return ['success' => true, 'message' => "Purged {$count} log entries older than {$days} days."];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return ['success' => false, 'message' => 'Purge failed: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an IP to the firewall blocklist.
|
|
*/
|
|
public function banIp(string $ip, string $reason = 'Banned from WAF log'): array
|
|
{
|
|
try
|
|
{
|
|
$db = $this->getDatabase();
|
|
|
|
$query = $db->getQuery(true)
|
|
->select($db->quoteName('params'))
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_firewall'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
|
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
|
|
$db->setQuery($query);
|
|
|
|
$params = new \Joomla\Registry\Registry($db->loadResult() ?? '{}');
|
|
$blocklist = json_decode($params->get('ip_blocklist', '[]'), true) ?: [];
|
|
|
|
// Check if already blocked
|
|
foreach ($blocklist as $entry)
|
|
{
|
|
if (($entry['ip'] ?? '') === $ip)
|
|
{
|
|
return ['success' => false, 'message' => $ip . ' is already blocked.'];
|
|
}
|
|
}
|
|
|
|
$blocklist[] = ['ip' => $ip, 'enabled' => '1', 'label' => $reason];
|
|
$params->set('ip_blocklist', json_encode($blocklist));
|
|
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__extensions'))
|
|
->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
|
|
->where($db->quoteName('element') . ' = ' . $db->quote('mokosuiteclient_firewall'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
|
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
|
)->execute();
|
|
|
|
return ['success' => true, 'message' => $ip . ' has been added to the IP blocklist.'];
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
return ['success' => false, 'message' => 'Ban failed: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
}
|