## 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
215 lines
7.4 KiB
PHP
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;
|
|
}
|
|
}
|