## Summary - remove remaining legacy scope query hint parsing from shared workspace and environment scoping seams so hubs only narrow via explicit `environment_id` - align canonical link generation across workspace hubs, provider connections, audit log, alerts, and decision register flows - add focused Spec 341 regression coverage for canonical link/query behavior and legacy alias rejection - include the Spec 341 artifacts and move the review screenshots into `specs/341-canonical-link-query-cleanup/artifacts/screenshots/` - ignore local `.playwright-mcp` browser tool output so it does not pollute future commits or pull requests ## Validation - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Navigation --filter=Spec341` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Navigation/Spec341CanonicalLinkQueryCleanupTest.php tests/Feature/Navigation/WorkspaceHubEnvironmentFilterContractTest.php tests/Feature/ProviderConnections/ProviderConnectionsWorkspaceHubContractTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - `git diff --check` ## Notes - Livewire v4 compliance unchanged - Filament provider registration remains in `apps/platform/bootstrap/providers.php` - no globally searchable resource behavior was changed in this slice - no destructive action behavior was changed - no new Filament assets; deploy `filament:assets` posture is unchanged Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #413
201 lines
6.9 KiB
PHP
201 lines
6.9 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\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)
|
|
) {
|
|
$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 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;
|
|
}
|
|
}
|