Files
server/core/lib/Controllers/FirewallController.php
T
2026-08-03 22:27:20 -04:00

306 lines
9.8 KiB
PHP

<?php
declare(strict_types=1);
namespace KTXC\Controllers;
use KTXC\Http\Request\Request;
use KTXC\Http\Response\JsonResponse;
use KTXC\Service\FirewallRuleConflictException;
use KTXC\Service\SystemFirewallLogService;
use KTXC\Service\SystemFirewallRuleService;
use KTXC\Service\SystemFirewallStatusService;
use KTXC\Service\TenantFirewallLogService;
use KTXC\Service\TenantFirewallRuleService;
use KTXC\Service\TenantFirewallStatusService;
use KTXF\Controller\ControllerAbstract;
use KTXF\Routing\Attributes\AuthenticatedRoute;
final class FirewallController extends ControllerAbstract
{
public function __construct(
private readonly TenantFirewallRuleService $tenantRules,
private readonly SystemFirewallRuleService $systemRules,
private readonly TenantFirewallLogService $tenantLogs,
private readonly SystemFirewallLogService $systemLogs,
private readonly TenantFirewallStatusService $tenantStatus,
private readonly SystemFirewallStatusService $systemStatus,
) {
}
#[AuthenticatedRoute(
'/firewall/rules',
name: 'firewall.tenant.rules.list',
permissions: [TenantFirewallRuleService::PERMISSION_READ],
)]
public function tenantRules(
string $status = 'active',
?string $type = null,
?string $action = null,
string $limit = '50',
string $offset = '0'
): JsonResponse {
return $this->queryResponse(
fn(int $parsedLimit, int $parsedOffset): array => $this->tenantRules->queryRules(
$status,
$type,
$action,
$parsedLimit,
$parsedOffset
),
$limit,
$offset
);
}
#[AuthenticatedRoute(
'/firewall/rules/{ruleId}',
name: 'firewall.tenant.rules.fetch',
permissions: [TenantFirewallRuleService::PERMISSION_READ],
)]
public function tenantRule(string $ruleId): JsonResponse
{
return $this->ruleResponse($this->tenantRules->fetchRule($ruleId));
}
#[AuthenticatedRoute(
'/firewall/effective-policy',
name: 'firewall.tenant.policy.effective',
permissions: [TenantFirewallRuleService::PERMISSION_READ],
)]
public function effectivePolicy(): JsonResponse
{
return new JsonResponse($this->tenantRules->effectivePolicy());
}
#[AuthenticatedRoute(
'/firewall/system/rules',
name: 'firewall.system.rules.list',
permissions: [SystemFirewallRuleService::PERMISSION_READ],
)]
public function systemRules(
string $status = 'active',
?string $type = null,
?string $action = null,
string $limit = '50',
string $offset = '0'
): JsonResponse {
return $this->queryResponse(
fn(int $parsedLimit, int $parsedOffset): array => $this->systemRules->queryRules(
$status,
$type,
$action,
$parsedLimit,
$parsedOffset
),
$limit,
$offset
);
}
#[AuthenticatedRoute(
'/firewall/system/rules/{ruleId}',
name: 'firewall.system.rules.fetch',
permissions: [SystemFirewallRuleService::PERMISSION_READ],
)]
public function systemRule(string $ruleId): JsonResponse
{
return $this->ruleResponse($this->systemRules->fetchRule($ruleId));
}
#[AuthenticatedRoute(
'/firewall/rules',
name: 'firewall.tenant.rules.create',
methods: ['POST'],
permissions: [TenantFirewallRuleService::PERMISSION_MANAGE],
)]
public function createTenantRule(
Request $request,
string $type,
string $action,
string $value,
string $reason,
?int $durationSeconds = null,
bool $confirmCurrentIp = false
): JsonResponse {
return $this->mutationResponse(fn() => $this->tenantRules->createRule(
$type,
$action,
$value,
$reason,
$durationSeconds,
$request->getClientIp(),
$confirmCurrentIp
));
}
#[AuthenticatedRoute(
'/firewall/system/rules',
name: 'firewall.system.rules.create',
methods: ['POST'],
permissions: [SystemFirewallRuleService::PERMISSION_MANAGE],
)]
public function createSystemRule(
Request $request,
string $type,
string $action,
string $value,
string $reason,
?int $durationSeconds = null,
bool $confirmCurrentIp = false
): JsonResponse {
return $this->mutationResponse(fn() => $this->systemRules->createRule(
$type,
$action,
$value,
$reason,
$durationSeconds,
$request->getClientIp(),
$confirmCurrentIp
));
}
#[AuthenticatedRoute(
'/firewall/logs',
name: 'firewall.tenant.logs.list',
permissions: [TenantFirewallLogService::PERMISSION_READ],
)]
public function tenantLogs(
?string $ipAddress = null,
?string $eventType = null,
?string $result = null,
?string $ruleId = null,
?string $ruleScope = null,
?string $from = null,
?string $to = null,
string $limit = '50',
string $offset = '0'
): JsonResponse {
return $this->queryResponse(
fn(int $parsedLimit, int $parsedOffset): array => $this->tenantLogs->query(
compact('ipAddress', 'eventType', 'result', 'ruleId', 'ruleScope', 'from', 'to'),
$parsedLimit,
$parsedOffset
),
$limit,
$offset
);
}
#[AuthenticatedRoute(
'/firewall/system/logs',
name: 'firewall.system.logs.list',
permissions: [SystemFirewallLogService::PERMISSION_READ],
)]
public function systemLogs(
?string $tenantId = null,
?string $ipAddress = null,
?string $eventType = null,
?string $result = null,
?string $ruleId = null,
?string $ruleScope = null,
?string $from = null,
?string $to = null,
string $limit = '50',
string $offset = '0'
): JsonResponse {
return $this->queryResponse(
fn(int $parsedLimit, int $parsedOffset): array => $this->systemLogs->query(
$tenantId,
compact('ipAddress', 'eventType', 'result', 'ruleId', 'ruleScope', 'from', 'to'),
$parsedLimit,
$parsedOffset
),
$limit,
$offset
);
}
#[AuthenticatedRoute(
'/firewall/metrics',
name: 'firewall.tenant.metrics.read',
permissions: [TenantFirewallLogService::PERMISSION_READ],
)]
public function tenantMetrics(?string $since = null): JsonResponse
{
return $this->readResponse(fn(): array => $this->tenantStatus->metrics($since));
}
#[AuthenticatedRoute(
'/firewall/configuration',
name: 'firewall.tenant.configuration.read',
permissions: [TenantFirewallStatusService::PERMISSION_SETTINGS_READ],
)]
public function tenantConfiguration(): JsonResponse
{
return new JsonResponse($this->tenantStatus->configuration());
}
#[AuthenticatedRoute(
'/firewall/system/metrics',
name: 'firewall.system.metrics.read',
permissions: [SystemFirewallLogService::PERMISSION_READ],
)]
public function systemMetrics(?string $tenantId = null, ?string $since = null): JsonResponse
{
return $this->readResponse(fn(): array => $this->systemStatus->metrics($tenantId, $since));
}
#[AuthenticatedRoute(
'/firewall/system/maintenance',
name: 'firewall.system.maintenance.read',
permissions: [SystemFirewallStatusService::PERMISSION_MAINTENANCE_READ],
)]
public function maintenanceStatus(): JsonResponse
{
return new JsonResponse($this->systemStatus->maintenanceStatus());
}
private function queryResponse(callable $query, string $limit, string $offset): JsonResponse
{
try {
if (!ctype_digit($limit) || !ctype_digit($offset)) {
throw new \InvalidArgumentException('Pagination values must be non-negative integers.');
}
return new JsonResponse($query((int)$limit, (int)$offset));
} catch (\InvalidArgumentException $error) {
return new JsonResponse(['error' => $error->getMessage()], JsonResponse::HTTP_BAD_REQUEST);
}
}
private function ruleResponse(?\JsonSerializable $rule): JsonResponse
{
if ($rule === null) {
return new JsonResponse(['error' => 'Firewall rule not found.'], JsonResponse::HTTP_NOT_FOUND);
}
return new JsonResponse($rule);
}
private function readResponse(callable $read): JsonResponse
{
try {
return new JsonResponse($read());
} catch (\InvalidArgumentException $error) {
return new JsonResponse(['error' => $error->getMessage()], JsonResponse::HTTP_BAD_REQUEST);
}
}
private function mutationResponse(callable $mutation): JsonResponse
{
try {
return new JsonResponse(['rule' => $mutation()], JsonResponse::HTTP_CREATED);
} catch (FirewallRuleConflictException $error) {
return new JsonResponse(['error' => [
'code' => $error->conflictCode,
'message' => $error->getMessage(),
]], JsonResponse::HTTP_CONFLICT);
} catch (\InvalidArgumentException $error) {
return new JsonResponse(['error' => [
'code' => 'invalid_firewall_rule',
'message' => $error->getMessage(),
]], JsonResponse::HTTP_BAD_REQUEST);
}
}
}