Files
MokoSuiteClient/source/packages/com_mokosuiteclient/admin/src/Model/WaflogModel.php
T
Jonathan Miller 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
Rename MokoSuite → MokoSuiteClient (full element rename)
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.
2026-06-15 05:19:13 -05:00

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()];
}
}
}