feat(firewall): add scoped log administration reads
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXT\Unit\Service;
|
||||
|
||||
use KTXC\Context\IdentityContextInterface;
|
||||
use KTXC\Context\TenantContextInterface;
|
||||
use KTXC\Models\Firewall\FirewallLogObject;
|
||||
use KTXC\Service\FirewallLogService;
|
||||
use KTXC\Service\SystemFirewallLogService;
|
||||
use KTXC\Service\TenantFirewallLogService;
|
||||
use KTXC\Stores\FirewallStore;
|
||||
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[AllowMockObjectsWithoutExpectations]
|
||||
final class FirewallLogServicesTest extends TestCase
|
||||
{
|
||||
#[TestDox('Tenant log reads derive tenant ownership and validated filters')]
|
||||
public function testTenantQuery(): void
|
||||
{
|
||||
$store = $this->createMock(FirewallStore::class);
|
||||
$tenant = $this->createStub(TenantContextInterface::class);
|
||||
$tenant->method('requireIdentifier')->willReturn('tenant-a');
|
||||
$identity = $this->createStub(IdentityContextInterface::class);
|
||||
$identity->method('hasPermission')->willReturn(true);
|
||||
$store->expects(self::once())
|
||||
->method('queryTenantLogs')
|
||||
->with(
|
||||
'tenant-a',
|
||||
self::callback(static fn(array $filters): bool =>
|
||||
$filters['eventType'] === FirewallLogObject::EVENT_AUTH_FAILURE
|
||||
&& $filters['from'] instanceof \DateTimeImmutable
|
||||
),
|
||||
25,
|
||||
10
|
||||
)
|
||||
->willReturn(['items' => [], 'total' => 0, 'limit' => 25, 'offset' => 10]);
|
||||
$service = new TenantFirewallLogService(new FirewallLogService($store), $tenant, $identity);
|
||||
|
||||
self::assertSame(0, $service->query([
|
||||
'eventType' => FirewallLogObject::EVENT_AUTH_FAILURE,
|
||||
'from' => '2026-08-01T00:00:00+00:00',
|
||||
], 25, 10)['total']);
|
||||
}
|
||||
|
||||
#[TestDox('System log reads may filter one tenant without changing ownership')]
|
||||
public function testSystemQuery(): void
|
||||
{
|
||||
$store = $this->createMock(FirewallStore::class);
|
||||
$identity = $this->createStub(IdentityContextInterface::class);
|
||||
$identity->method('hasPermission')->willReturn(true);
|
||||
$store->expects(self::once())
|
||||
->method('querySystemLogs')
|
||||
->with('tenant-a', self::isArray(), 50, 0)
|
||||
->willReturn(['items' => [], 'total' => 0, 'limit' => 50, 'offset' => 0]);
|
||||
$service = new SystemFirewallLogService(new FirewallLogService($store), $identity);
|
||||
|
||||
self::assertSame(0, $service->query('tenant-a', [], 50, 0)['total']);
|
||||
}
|
||||
|
||||
#[TestDox('Log queries reject invalid filters before database access')]
|
||||
public function testValidation(): void
|
||||
{
|
||||
$store = $this->createMock(FirewallStore::class);
|
||||
$store->expects(self::never())->method('queryTenantLogs');
|
||||
$query = new FirewallLogService($store);
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$query->tenant('tenant-a', ['ipAddress' => 'not-an-ip'], 50, 0);
|
||||
}
|
||||
|
||||
#[TestDox('Log boundaries enforce dedicated read permissions')]
|
||||
public function testPermissions(): void
|
||||
{
|
||||
$store = $this->createMock(FirewallStore::class);
|
||||
$identity = $this->createStub(IdentityContextInterface::class);
|
||||
$identity->method('hasPermission')->willReturn(false);
|
||||
$tenant = $this->createStub(TenantContextInterface::class);
|
||||
$store->expects(self::never())->method('queryTenantLogs');
|
||||
$store->expects(self::never())->method('querySystemLogs');
|
||||
|
||||
try {
|
||||
(new TenantFirewallLogService(new FirewallLogService($store), $tenant, $identity))->query([]);
|
||||
self::fail('Tenant log read should be rejected.');
|
||||
} catch (\RuntimeException $error) {
|
||||
self::assertStringContainsString(TenantFirewallLogService::PERMISSION_READ, $error->getMessage());
|
||||
}
|
||||
|
||||
$this->expectExceptionMessage(SystemFirewallLogService::PERMISSION_READ);
|
||||
(new SystemFirewallLogService(new FirewallLogService($store), $identity))->query(null, []);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user