Files
server/core/lib/Service/FirewallLogService.php
T
2026-08-03 22:35:45 -04:00

126 lines
4.3 KiB
PHP

<?php
declare(strict_types=1);
namespace KTXC\Service;
use KTXC\Models\Firewall\FirewallLogObject;
use KTXC\Models\Firewall\FirewallRuleObject;
use KTXC\Stores\FirewallStore;
final class FirewallLogService
{
public const MAX_LIMIT = 100;
private const EVENT_TYPES = [
FirewallLogObject::EVENT_AUTH_FAILURE,
FirewallLogObject::EVENT_RATE_LIMIT,
FirewallLogObject::EVENT_BRUTE_FORCE,
FirewallLogObject::EVENT_SUSPICIOUS,
FirewallLogObject::EVENT_RULE_MATCH,
FirewallLogObject::EVENT_ACCESS_CHECK,
FirewallLogObject::EVENT_RULE_CREATED,
FirewallLogObject::EVENT_RULE_EXTENDED,
FirewallLogObject::EVENT_RULE_ENABLED,
FirewallLogObject::EVENT_RULE_DISABLED,
FirewallLogObject::EVENT_RULE_REMOVED,
];
public function __construct(private readonly FirewallStore $store)
{
}
public function tenant(string $tenantId, array $filters, int $limit, int $offset): array
{
return $this->store->queryTenantLogs(
$tenantId,
$this->validate($filters, $limit, $offset),
$limit,
$offset
);
}
public function system(?string $tenantId, array $filters, int $limit, int $offset): array
{
if ($tenantId !== null && ($tenantId === '' || strlen($tenantId) > 128)) {
throw new \InvalidArgumentException('Invalid tenant filter.');
}
return $this->store->querySystemLogs(
$tenantId,
$this->validate($filters, $limit, $offset),
$limit,
$offset
);
}
private function validate(array $filters, int $limit, int $offset): array
{
if ($limit < 1 || $limit > self::MAX_LIMIT || $offset < 0) {
throw new \InvalidArgumentException('Pagination requires limit 1-100 and offset 0 or greater.');
}
$ipAddress = self::nullableString($filters, 'ipAddress');
if ($ipAddress !== null && filter_var($ipAddress, FILTER_VALIDATE_IP) === false) {
throw new \InvalidArgumentException('Invalid IP address filter.');
}
$eventType = self::nullableString($filters, 'eventType');
if ($eventType !== null && !in_array($eventType, self::EVENT_TYPES, true)) {
throw new \InvalidArgumentException('Invalid firewall event type filter.');
}
$result = self::nullableString($filters, 'result');
if ($result !== null && !in_array($result, [
FirewallLogObject::RESULT_ALLOWED,
FirewallLogObject::RESULT_BLOCKED,
FirewallLogObject::RESULT_RECORDED,
], true)) {
throw new \InvalidArgumentException('Invalid firewall result filter.');
}
$ruleScope = self::nullableString($filters, 'ruleScope');
if ($ruleScope !== null && !in_array($ruleScope, [
FirewallRuleObject::SCOPE_TENANT,
FirewallRuleObject::SCOPE_SYSTEM,
], true)) {
throw new \InvalidArgumentException('Invalid rule scope filter.');
}
$from = self::date($filters, 'from');
$to = self::date($filters, 'to');
if ($from !== null && $to !== null && $from > $to) {
throw new \InvalidArgumentException('The from date must not be later than the to date.');
}
return [
'ipAddress' => $ipAddress,
'eventType' => $eventType,
'result' => $result,
'ruleId' => self::nullableString($filters, 'ruleId'),
'ruleScope' => $ruleScope,
'from' => $from,
'to' => $to,
];
}
private static function nullableString(array $filters, string $key): ?string
{
$value = $filters[$key] ?? null;
if ($value === null) {
return null;
}
if (!is_string($value) || $value === '' || strlen($value) > 255) {
throw new \InvalidArgumentException("Invalid {$key} filter.");
}
return $value;
}
private static function date(array $filters, string $key): ?\DateTimeImmutable
{
$value = self::nullableString($filters, $key);
if ($value === null) {
return null;
}
try {
return new \DateTimeImmutable($value);
} catch (\Exception) {
throw new \InvalidArgumentException("Invalid {$key} date filter.");
}
}
}