TenantAtlas/apps/platform/app/Support/Middleware/EnsureEnvironmentContextSelected.php
ahmido ddf7c15c52 feat: enforce environment-owned baseline compare routing (#374)
## 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
2026-05-16 20:45:39 +00:00

215 lines
7.4 KiB
PHP

<?php
namespace App\Support\Middleware;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Support\Navigation\AdminSurfaceScope;
use App\Support\Navigation\NavigationScope;
use App\Support\Navigation\WorkspaceHubRegistry;
use App\Support\Navigation\WorkspaceSidebarNavigation;
use App\Support\OperateHub\OperateHubShell;
use App\Support\Workspaces\WorkspaceContext;
use Closure;
use Filament\Facades\Filament;
use Filament\Navigation\NavigationBuilder;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureEnvironmentContextSelected
{
/**
* @param Closure(Request): Response $next
*/
public function handle(Request $request, Closure $next): Response
{
$panel = Filament::getCurrentOrDefaultPanel();
$resolvedContext = app(OperateHubShell::class)->resolvedContext($request);
$path = '/'.ltrim($request->path(), '/');
$workspaceContext = app(WorkspaceContext::class);
$workspaceId = $workspaceContext->currentWorkspaceId($request);
$existingTenant = Filament::getTenant();
if ($existingTenant instanceof ManagedEnvironment && $workspaceId !== null && (int) $existingTenant->workspace_id !== (int) $workspaceId) {
Filament::setTenant(null, true);
$existingTenant = null;
}
$user = $request->user();
if ($existingTenant instanceof ManagedEnvironment && $user instanceof User && ! $user->canAccessTenant($existingTenant)) {
Filament::setTenant(null, true);
$existingTenant = null;
}
if ($existingTenant instanceof ManagedEnvironment && ($existingTenant->isRemovedFromWorkspace() || $existingTenant->workspace?->isClosed())) {
Filament::setTenant(null, true);
$existingTenant = null;
}
if ($this->isLivewireUpdatePath($path)) {
$refererPath = parse_url((string) $request->headers->get('referer', ''), PHP_URL_PATH) ?? '';
$refererPath = '/'.ltrim((string) $refererPath, '/');
if ($this->isCanonicalWorkspaceRecordViewerPath($refererPath)) {
$this->configureNavigationForRequest($panel, $request);
return $next($request);
}
if ($this->isWorkspaceScopedPageWithTenant($refererPath)) {
$this->configureNavigationForRequest($panel, $request);
return $next($request);
}
}
if ($this->isCanonicalWorkspaceRecordViewerPath($path)) {
$this->configureNavigationForRequest($panel, $request);
return $next($request);
}
if (in_array($path, ['/admin/findings/my-work', '/admin/findings/intake', '/admin/findings/hygiene'], true)) {
$this->configureNavigationForRequest($panel, $request);
return $next($request);
}
if (preg_match('#^/admin/workspaces/[^/]+/operations(?:/[^/]+)?$#', $path) === 1) {
$this->configureNavigationForRequest($panel, $request);
return $next($request);
}
if (
! $resolvedContext->hasTenant()
&& $this->adminPathRequiresTenantSelection($path)
) {
if ($this->requestHasExplicitTenantHint($request)) {
abort(404);
}
$workspace = $workspaceContext->currentWorkspace($request);
if ($workspace !== null) {
return redirect()->route('admin.workspace.managed-environments.index', ['workspace' => $workspace]);
}
return redirect()->route('filament.admin.pages.choose-environment');
}
if ($resolvedContext->pageCategory === AdminSurfaceScope::EnvironmentBound && ! $resolvedContext->hasTenant()) {
abort(404);
}
if ($resolvedContext->hasTenant() && $resolvedContext->tenant?->isRemovedFromWorkspace() && str_starts_with($path, '/admin/workspaces/')) {
abort(404);
}
if ($resolvedContext->hasTenant() && $resolvedContext->tenant?->workspace?->isClosed() && str_starts_with($path, '/admin/workspaces/')) {
abort(404);
}
if (
$resolvedContext->hasTenant()
&& (
! $this->isWorkspaceScopedPageWithTenant($path)
&& $resolvedContext->pageCategory === AdminSurfaceScope::EnvironmentBound
)
) {
Filament::setTenant($resolvedContext->tenant, true);
} elseif (! $resolvedContext->hasTenant()) {
Filament::setTenant(null, true);
}
if (
str_starts_with($path, '/admin/workspaces/')
|| in_array($path, ['/admin', '/admin/choose-workspace', '/admin/choose-environment', '/admin/no-access', '/admin/alerts', '/admin/audit-log', '/admin/onboarding', '/admin/settings/workspace', '/admin/findings/my-work', '/admin/findings/intake', '/admin/findings/hygiene'], true)
) {
$this->configureNavigationForRequest($panel, $request);
return $next($request);
}
if (filled(Filament::getTenant())) {
$this->configureNavigationForRequest($panel, $request);
return $next($request);
}
if (! $user instanceof User) {
$this->configureNavigationForRequest($panel, $request);
return $next($request);
}
$this->configureNavigationForRequest($panel, $request);
return $next($request);
}
private function configureNavigationForRequest(\Filament\Panel $panel, Request $request): void
{
if (NavigationScope::isEnvironmentSurface($request)) {
$panel->navigation(true);
return;
}
$panel->navigation(function (WorkspaceSidebarNavigation $navigation): NavigationBuilder {
return $navigation->build();
});
}
private function isWorkspaceScopedPageWithTenant(string $path): bool
{
return preg_match('#^/admin/workspaces/[^/]+/environments/[^/]+/(required-permissions|diagnostics|access-scopes|baseline-compare)$#', $path) === 1;
}
private function isLivewireUpdatePath(string $path): bool
{
return preg_match('#^/livewire(?:-[^/]+)?/update$#', $path) === 1;
}
private function isCanonicalWorkspaceRecordViewerPath(string $path): bool
{
return AdminSurfaceScope::fromPath($path) === AdminSurfaceScope::CanonicalWorkspaceRecordViewer;
}
private function requestHasExplicitTenantHint(Request $request): bool
{
if (WorkspaceHubRegistry::isWorkspaceHubPath('/'.ltrim((string) $request->path(), '/'))) {
return false;
}
return filled($request->query('tenant')) || filled($request->query('managed_environment_id'));
}
private function adminPathRequiresTenantSelection(string $path): bool
{
if (! str_starts_with($path, '/admin/')) {
return false;
}
if (str_starts_with($path, '/admin/finding-exceptions/queue')) {
return false;
}
if (str_starts_with($path, '/admin/findings/my-work')) {
return false;
}
if (str_starts_with($path, '/admin/findings/intake')) {
return false;
}
if (str_starts_with($path, '/admin/findings/hygiene')) {
return false;
}
return preg_match('#^/admin/(inventory|policies|policy-versions|backup-sets|backup-schedules|findings|finding-exceptions)(/|$)#', $path) === 1;
}
}