## Summary - move Baseline Compare onto the canonical workspace plus environment owned route instead of workspace-style access - remove legacy environment query and remembered-context fallback paths from the affected Baseline Compare entry points and shell handling - update related navigation, support links, and regression coverage for admin surface scope and managed environment route contracts - add Spec 319 artifacts for the environment-owned surface routing and shell context contract ## Testing - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/BaselineCompareEnvironmentRouteContractTest.php tests/Feature/Filament/BaselineCompareLandingAdminTenantParityTest.php tests/Feature/Filament/BaselineCompareLandingDuplicateNamesBannerTest.php tests/Feature/Filament/BaselineCompareLandingRbacLabelsTest.php tests/Feature/Filament/BaselineCompareLandingStartSurfaceTest.php tests/Feature/Filament/BaselineCompareLandingWhyNoFindingsTest.php tests/Feature/Filament/PanelNavigationSegregationTest.php tests/Feature/Guards/ManagedEnvironmentCanonicalRouteContractTest.php tests/Feature/Navigation/WorkspaceHubRegistryTest.php tests/Feature/Rbac/BaselineCompareMatrixAuthorizationTest.php tests/Feature/Rbac/DriftLandingUiEnforcementTest.php tests/Unit/Tenants/AdminSurfaceScopeTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #374
146 lines
5.3 KiB
PHP
146 lines
5.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\BaselineCompareLanding;
|
|
use App\Filament\Resources\BaselineProfileResource;
|
|
use App\Filament\Resources\FindingResource;
|
|
use App\Models\User;
|
|
use App\Services\Auth\CapabilityResolver;
|
|
use App\Services\Auth\WorkspaceCapabilityResolver;
|
|
use App\Support\Auth\Capabilities;
|
|
use App\Support\Navigation\CanonicalNavigationContext;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Tests\Feature\Concerns\BuildsBaselineCompareMatrixFixtures;
|
|
|
|
uses(RefreshDatabase::class, BuildsBaselineCompareMatrixFixtures::class);
|
|
|
|
it('returns 404 for non-members on the workspace baseline compare matrix', function (): void {
|
|
$fixture = $this->makeBaselineCompareMatrixFixture();
|
|
$nonMember = User::factory()->create();
|
|
|
|
$this->actingAs($nonMember)->withSession([
|
|
\App\Support\Workspaces\WorkspaceContext::SESSION_KEY => (int) $fixture['workspace']->getKey(),
|
|
]);
|
|
|
|
$this->get(BaselineProfileResource::compareMatrixUrl($fixture['profile']))
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('returns 403 for workspace members missing baseline view capability on the matrix route', function (): void {
|
|
$fixture = $this->makeBaselineCompareMatrixFixture();
|
|
$viewer = User::factory()->create();
|
|
|
|
\App\Models\WorkspaceMembership::factory()->create([
|
|
'workspace_id' => (int) $fixture['workspace']->getKey(),
|
|
'user_id' => (int) $viewer->getKey(),
|
|
'role' => 'readonly',
|
|
]);
|
|
|
|
$resolver = \Mockery::mock(WorkspaceCapabilityResolver::class);
|
|
$resolver->shouldReceive('isMember')->andReturnTrue();
|
|
$resolver->shouldReceive('can')->andReturnFalse();
|
|
app()->instance(WorkspaceCapabilityResolver::class, $resolver);
|
|
|
|
$this->actingAs($viewer)->withSession([
|
|
\App\Support\Workspaces\WorkspaceContext::SESSION_KEY => (int) $fixture['workspace']->getKey(),
|
|
]);
|
|
|
|
$this->get(BaselineProfileResource::compareMatrixUrl($fixture['profile']))
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('returns 404 for matrix tenant drilldowns when the actor is not a tenant member', function (): void {
|
|
$fixture = $this->makeBaselineCompareMatrixFixture();
|
|
$nonMember = User::factory()->create();
|
|
|
|
$this->actingAs($nonMember)->withSession([
|
|
\App\Support\Workspaces\WorkspaceContext::SESSION_KEY => (int) $fixture['workspace']->getKey(),
|
|
]);
|
|
|
|
$query = CanonicalNavigationContext::forBaselineCompareMatrix(
|
|
$fixture['profile'],
|
|
subjectKey: 'wifi-corp-profile',
|
|
)->toQuery();
|
|
|
|
$this->get(BaselineCompareLanding::getUrl(parameters: $query, panel: 'admin', tenant: $fixture['visibleTenant']))
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('returns 404 for matrix tenant drilldowns when tenant view capability is missing', function (): void {
|
|
$fixture = $this->makeBaselineCompareMatrixFixture();
|
|
|
|
$resolver = \Mockery::mock(CapabilityResolver::class);
|
|
$resolver->shouldReceive('isMember')->andReturnTrue();
|
|
$resolver->shouldReceive('can')->andReturnFalse();
|
|
app()->instance(CapabilityResolver::class, $resolver);
|
|
|
|
$this->actingAs($fixture['user'])->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $fixture['workspace']->getKey(),
|
|
]);
|
|
$fixture['visibleTenant']->makeCurrent();
|
|
|
|
$query = CanonicalNavigationContext::forBaselineCompareMatrix(
|
|
$fixture['profile'],
|
|
subjectKey: 'wifi-corp-profile',
|
|
tenant: $fixture['visibleTenant'],
|
|
)->toQuery();
|
|
|
|
$this->get(BaselineCompareLanding::getUrl(parameters: $query, panel: 'admin', tenant: $fixture['visibleTenant']))
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('returns 403 for matrix finding drilldowns when findings view capability is missing', function (): void {
|
|
$fixture = $this->makeBaselineCompareMatrixFixture();
|
|
|
|
$run = $this->makeBaselineCompareMatrixRun(
|
|
$fixture['visibleTenant'],
|
|
$fixture['profile'],
|
|
$fixture['snapshot'],
|
|
);
|
|
|
|
$finding = $this->makeBaselineCompareMatrixFinding(
|
|
$fixture['visibleTenant'],
|
|
$fixture['profile'],
|
|
$run,
|
|
'wifi-corp-profile',
|
|
);
|
|
|
|
$this->actingAs($fixture['user']);
|
|
$fixture['visibleTenant']->makeCurrent();
|
|
|
|
Gate::define(Capabilities::TENANT_FINDINGS_VIEW, fn (): bool => false);
|
|
|
|
$query = CanonicalNavigationContext::forBaselineCompareMatrix(
|
|
$fixture['profile'],
|
|
subjectKey: 'wifi-corp-profile',
|
|
tenant: $fixture['visibleTenant'],
|
|
)->toQuery();
|
|
|
|
$this->get(FindingResource::getUrl('view', [
|
|
'record' => $finding,
|
|
...$query,
|
|
], tenant: $fixture['visibleTenant']))
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('drops foreign compare-context launch data instead of leaking another workspace profile', function (): void {
|
|
$fixture = $this->makeBaselineCompareMatrixFixture();
|
|
|
|
$foreignProfile = \App\Models\BaselineProfile::factory()->create();
|
|
|
|
$this->actingAs($fixture['user']);
|
|
$fixture['visibleTenant']->makeCurrent();
|
|
|
|
$component = baselineCompareLandingLivewire($fixture['visibleTenant'], [
|
|
'baseline_profile_id' => (int) $foreignProfile->getKey(),
|
|
'subject_key' => 'wifi-corp-profile',
|
|
]);
|
|
|
|
expect($component->instance()->openCompareMatrixUrl())
|
|
->toStartWith(BaselineProfileResource::compareMatrixUrl($fixture['profile']))
|
|
->not->toContain((string) $foreignProfile->getKey());
|
|
});
|