feat(firewall): add operational status reads

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-08-03 22:22:07 -04:00
parent d919b70a2e
commit 6700ff145d
10 changed files with 356 additions and 1 deletions
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace KTXC\Service;
use KTXC\Stores\FirewallStore;
final class FirewallStatusService
{
public function __construct(private readonly FirewallStore $store)
{
}
public function tenantMetrics(string $tenantId, ?string $since = null): array
{
$sinceDate = self::date($since);
return [
'tenantId' => $tenantId,
'blockedRequests' => $this->store->countBlockedRequests($tenantId, $sinceDate),
'since' => $sinceDate?->format(\DateTimeInterface::ATOM),
];
}
public function systemMetrics(?string $tenantId = null, ?string $since = null): array
{
if ($tenantId !== null && ($tenantId === '' || strlen($tenantId) > 128)) {
throw new \InvalidArgumentException('Invalid tenant filter.');
}
$sinceDate = self::date($since);
return [
'tenantId' => $tenantId,
'blockedRequests' => $this->store->countSystemBlockedRequests($tenantId, $sinceDate),
'since' => $sinceDate?->format(\DateTimeInterface::ATOM),
];
}
public function maintenanceStatus(): array
{
return $this->store->maintenanceStatus() ?? [
'status' => 'never_run',
'startedAt' => null,
'completedAt' => null,
'result' => null,
'error' => null,
];
}
private static function date(?string $value): ?\DateTimeImmutable
{
if ($value === null) {
return null;
}
if ($value === '' || strlen($value) > 255) {
throw new \InvalidArgumentException('Invalid since date filter.');
}
try {
return new \DateTimeImmutable($value);
} catch (\Exception) {
throw new \InvalidArgumentException('Invalid since date filter.');
}
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace KTXC\Service;
use KTXC\Context\IdentityContextInterface;
final class SystemFirewallStatusService
{
public const PERMISSION_MAINTENANCE_READ = 'firewall.system.maintenance.read';
public function __construct(
private readonly FirewallStatusService $status,
private readonly IdentityContextInterface $identity,
) {
}
public function metrics(?string $tenantId = null, ?string $since = null): array
{
$this->requirePermission(SystemFirewallLogService::PERMISSION_READ);
return $this->status->systemMetrics($tenantId, $since);
}
public function maintenanceStatus(): array
{
$this->requirePermission(self::PERMISSION_MAINTENANCE_READ);
return $this->status->maintenanceStatus();
}
private function requirePermission(string $permission): void
{
if (!$this->identity->hasPermission($permission)) {
throw new \RuntimeException("Missing required permission: {$permission}");
}
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace KTXC\Service;
use KTXC\Context\IdentityContextInterface;
use KTXC\Context\TenantContextInterface;
final class TenantFirewallStatusService
{
public const PERMISSION_SETTINGS_READ = 'firewall.tenant.settings.read';
public function __construct(
private readonly FirewallStatusService $status,
private readonly TenantContextInterface $tenant,
private readonly IdentityContextInterface $identity,
) {
}
public function metrics(?string $since = null): array
{
$this->requirePermission(TenantFirewallLogService::PERMISSION_READ);
return $this->status->tenantMetrics($this->tenant->requireIdentifier(), $since);
}
public function configuration(): array
{
$this->requirePermission(self::PERMISSION_SETTINGS_READ);
return $this->tenant->configuration()?->firewall()->jsonSerialize()
?? [
'enabled' => true,
'maxAuthFailures' => 5,
'authFailureWindow' => 300,
'autoBlockDuration' => 3600,
];
}
private function requirePermission(string $permission): void
{
if (!$this->identity->hasPermission($permission)) {
throw new \RuntimeException("Missing required permission: {$permission}");
}
}
}