Automated PR created via MCP by Copilot on user request: "pr gegen platform-dev". Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #332
151 lines
6.0 KiB
PHP
151 lines
6.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\System\Pages\Directory\ViewWorkspace;
|
|
use App\Models\AuditLog;
|
|
use App\Models\PlatformUser;
|
|
use App\Models\SupportAccessGrant;
|
|
use App\Models\Tenant;
|
|
use App\Models\Workspace;
|
|
use App\Services\Auth\BreakGlassSession;
|
|
use App\Support\Audit\AuditActionId;
|
|
use App\Support\Auth\PlatformCapabilities;
|
|
use Filament\Actions\Action;
|
|
use Filament\Facades\Filament;
|
|
use Livewire\Livewire;
|
|
|
|
beforeEach(function (): void {
|
|
Filament::setCurrentPanel('system');
|
|
Filament::bootCurrentPanel();
|
|
|
|
config()->set('tenantpilot.support_access.max_ttl_minutes', 120);
|
|
config()->set('tenantpilot.break_glass.enabled', true);
|
|
config()->set('tenantpilot.break_glass.ttl_minutes', 15);
|
|
});
|
|
|
|
function spec276_system_platform_user(array $extraCapabilities = []): PlatformUser
|
|
{
|
|
return PlatformUser::factory()->create([
|
|
'capabilities' => array_values(array_unique([
|
|
PlatformCapabilities::ACCESS_SYSTEM_PANEL,
|
|
PlatformCapabilities::DIRECTORY_VIEW,
|
|
...$extraCapabilities,
|
|
])),
|
|
]);
|
|
}
|
|
|
|
it('lets an authorized platform operator start and end audit-view support access from the workspace detail page', function (): void {
|
|
$workspace = Workspace::factory()->create(['name' => 'Acme Recovery']);
|
|
$platformUser = spec276_system_platform_user([
|
|
PlatformCapabilities::SUPPORT_ACCESS_MANAGE,
|
|
]);
|
|
|
|
$component = Livewire::actingAs($platformUser, 'platform')
|
|
->test(ViewWorkspace::class, ['workspace' => $workspace])
|
|
->assertActionVisible('request_support_access')
|
|
->assertActionExists('request_support_access', fn (Action $action): bool => $action->isConfirmationRequired())
|
|
->callAction('request_support_access', data: [
|
|
'scope' => SupportAccessGrant::SCOPE_AUDIT_VIEW,
|
|
'reason' => 'Investigate managed tenant audit evidence',
|
|
'ttl_minutes' => 45,
|
|
])
|
|
->assertNotified('Support access active')
|
|
->assertActionVisible('end_support_access')
|
|
->assertActionExists('end_support_access', fn (Action $action): bool => $action->isConfirmationRequired());
|
|
|
|
$grant = SupportAccessGrant::query()
|
|
->where('workspace_id', (int) $workspace->getKey())
|
|
->firstOrFail();
|
|
|
|
expect($grant->status)->toBe(SupportAccessGrant::STATUS_ACTIVE)
|
|
->and($grant->reason)->toBe('Investigate managed tenant audit evidence');
|
|
|
|
$component
|
|
->callAction('end_support_access')
|
|
->assertNotified('Support access ended');
|
|
|
|
expect($grant->fresh()->status)->toBe(SupportAccessGrant::STATUS_ENDED);
|
|
|
|
expect(AuditLog::query()
|
|
->where('workspace_id', (int) $workspace->getKey())
|
|
->whereIn('action', [
|
|
AuditActionId::SupportAccessRequested->value,
|
|
AuditActionId::SupportAccessActivated->value,
|
|
AuditActionId::SupportAccessEnded->value,
|
|
])
|
|
->count())->toBe(3);
|
|
});
|
|
|
|
it('hides support-access mutations from platform users without support-access capability', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$platformUser = spec276_system_platform_user();
|
|
|
|
Livewire::actingAs($platformUser, 'platform')
|
|
->test(ViewWorkspace::class, ['workspace' => $workspace])
|
|
->assertActionHidden('request_support_access')
|
|
->assertActionHidden('end_support_access');
|
|
});
|
|
|
|
it('activates ownerless workspace-recovery support access only through a break-glass waiver', function (): void {
|
|
Tenant::factory()->create([
|
|
'tenant_id' => null,
|
|
'external_id' => 'platform',
|
|
'name' => 'Platform',
|
|
]);
|
|
|
|
$workspace = Workspace::factory()->create(['name' => 'Ownerless Support Workspace']);
|
|
$platformUser = spec276_system_platform_user([
|
|
PlatformCapabilities::SUPPORT_ACCESS_MANAGE,
|
|
PlatformCapabilities::USE_BREAK_GLASS,
|
|
]);
|
|
|
|
$this->actingAs($platformUser, 'platform');
|
|
app(BreakGlassSession::class)->start($platformUser, 'Ownerless recovery support access');
|
|
|
|
Livewire::actingAs($platformUser, 'platform')
|
|
->test(ViewWorkspace::class, ['workspace' => $workspace])
|
|
->callAction('request_support_access', data: [
|
|
'scope' => SupportAccessGrant::SCOPE_WORKSPACE_RECOVERY,
|
|
'reason' => 'Recover ownerless workspace safely',
|
|
'ttl_minutes' => 30,
|
|
'waiver_reason' => 'Verified the workspace has no owner membership',
|
|
])
|
|
->assertNotified('Support access active');
|
|
|
|
$grant = SupportAccessGrant::query()
|
|
->where('workspace_id', (int) $workspace->getKey())
|
|
->where('scope', SupportAccessGrant::SCOPE_WORKSPACE_RECOVERY)
|
|
->firstOrFail();
|
|
|
|
expect($grant->status)->toBe(SupportAccessGrant::STATUS_ACTIVE)
|
|
->and($grant->approval_mode)->toBe(SupportAccessGrant::APPROVAL_MODE_OWNERLESS_WAIVER)
|
|
->and($grant->waiver_reason)->toBe('Verified the workspace has no owner membership');
|
|
|
|
expect(AuditLog::query()
|
|
->where('workspace_id', (int) $workspace->getKey())
|
|
->where('action', AuditActionId::SupportAccessOwnerlessWaiverUsed->value)
|
|
->exists())->toBeTrue();
|
|
});
|
|
|
|
it('renders current support-access posture on the system workspace detail page', function (): void {
|
|
$workspace = Workspace::factory()->create(['name' => 'Northwind Workspace']);
|
|
$platformUser = spec276_system_platform_user([
|
|
PlatformCapabilities::SUPPORT_ACCESS_MANAGE,
|
|
]);
|
|
|
|
SupportAccessGrant::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'requested_by_platform_user_id' => (int) $platformUser->getKey(),
|
|
'scope' => SupportAccessGrant::SCOPE_AUDIT_VIEW,
|
|
'reason' => 'Inspect audit evidence for a support case',
|
|
]);
|
|
|
|
$this->actingAs($platformUser, 'platform')
|
|
->get(ViewWorkspace::getUrl(panel: 'system', parameters: ['workspace' => $workspace]))
|
|
->assertSuccessful()
|
|
->assertSee('Support access')
|
|
->assertSee('Audit trail review')
|
|
->assertSee('Inspect audit evidence for a support case');
|
|
});
|