*/ private const FORBIDDEN_QUERY_KEYS = [ 'tenant', 'tenant_id', 'managed_environment_id', 'environment_id', 'environment', 'tenant_scope', 'tableFilters', ]; /** * @var list */ private const ENVIRONMENT_FILTER_KEYS = [ 'tenant', 'tenant_id', 'managed_environment_id', 'environment_id', 'environment', 'tenant_scope', ]; /** * @var list */ private const ENVIRONMENT_FILTER_QUERY_KEYS = [ 'environment_id', ]; /** * @var array */ private const HUBS = [ 'workspace_home' => [ 'label' => 'Workspace Overview', 'path' => '/admin', 'pattern' => '#^/admin/?$#', ], 'workspace_overview' => [ 'label' => 'Workspace Overview', 'path' => '/admin/workspaces/{workspace}/overview', 'pattern' => '#^/admin/workspaces/[^/]+/overview/?$#', ], 'operations' => [ 'label' => 'Operations', 'path' => '/admin/workspaces/{workspace}/operations', 'pattern' => '#^/admin/workspaces/[^/]+/operations/?$#', ], 'provider_connections' => [ 'label' => 'Provider Connections', 'path' => '/admin/provider-connections', 'pattern' => '#^/admin/provider-connections(?:/.*)?$#', ], 'finding_exceptions_queue' => [ 'label' => 'Finding Exceptions Queue', 'path' => '/admin/finding-exceptions/queue', 'pattern' => '#^/admin/finding-exceptions/queue/?$#', ], 'evidence_overview' => [ 'label' => 'Evidence Overview', 'path' => '/admin/evidence/overview', 'pattern' => '#^/admin/evidence/overview/?$#', ], 'review_register' => [ 'label' => 'Review Register', 'path' => '/admin/reviews', 'pattern' => '#^/admin/reviews/?$#', ], 'customer_review_workspace' => [ 'label' => 'Customer Review Workspace', 'path' => '/admin/reviews/workspace', 'pattern' => '#^/admin/reviews/workspace/?$#', ], 'governance_inbox' => [ 'label' => 'Governance Inbox', 'path' => '/admin/governance/inbox', 'pattern' => '#^/admin/governance/inbox/?$#', ], 'decision_register' => [ 'label' => 'Decision Register', 'path' => '/admin/governance/decisions', 'pattern' => '#^/admin/governance/decisions/?$#', ], 'audit_log' => [ 'label' => 'Audit Log', 'path' => '/admin/audit-log', 'pattern' => '#^/admin/audit-log/?$#', ], 'alerts' => [ 'label' => 'Alerts', 'path' => '/admin/alerts', 'pattern' => '#^/admin/alerts/?$#', ], 'alert_deliveries' => [ 'label' => 'Alert Deliveries', 'path' => '/admin/alerts/alert-deliveries', 'pattern' => '#^/admin/alerts/alert-deliveries(?:/.*)?$#', ], 'alert_rules' => [ 'label' => 'Alert Rules', 'path' => '/admin/alerts/alert-rules', 'pattern' => '#^/admin/alerts/alert-rules(?:/.*)?$#', ], 'alert_destinations' => [ 'label' => 'Alert Destinations', 'path' => '/admin/alerts/alert-destinations', 'pattern' => '#^/admin/alerts/alert-destinations(?:/.*)?$#', ], 'workspace_settings' => [ 'label' => 'Workspace Settings', 'path' => '/admin/settings/workspace', 'pattern' => '#^/admin/settings/workspace/?$#', ], 'manage_workspaces' => [ 'label' => 'Manage Workspaces', 'path' => '/admin/workspaces', 'pattern' => '#^/admin/workspaces/?$#', ], 'managed_environments_landing' => [ 'label' => 'Managed Environments Landing', 'path' => '/admin/workspaces/{workspace}/environments', 'pattern' => '#^/admin/workspaces/[^/]+/environments/?$#', ], ]; /** * @var array */ private const EXCLUSIONS = [ 'environment_dashboard' => [ 'label' => 'Environment Dashboard', 'path' => '/admin/workspaces/{workspace}/environments/{environment}', 'pattern' => '#^/admin/workspaces/[^/]+/environments/[^/]+(?:/.*)?$#', ], 'stored_reports_environment_routes' => [ 'label' => 'Stored Reports environment routes', 'path' => '/admin/workspaces/{workspace}/environments/{environment}/stored-reports', 'pattern' => '#^/admin/workspaces/[^/]+/environments/[^/]+/stored-reports(?:/.*)?$#', ], 'support_request_action_surface' => [ 'label' => 'Support Request action surface', 'path' => 'modal/action-only', 'pattern' => '#^$#', ], ]; /** * @return array */ public static function entries(): array { return self::HUBS; } /** * @return array */ public static function exclusions(): array { return self::EXCLUSIONS; } /** * @return list */ public static function forbiddenQueryKeys(): array { return self::FORBIDDEN_QUERY_KEYS; } /** * @return list */ public static function environmentLikeFilterKeys(): array { return self::ENVIRONMENT_FILTER_KEYS; } public static function isWorkspaceHubPath(string $path): bool { $normalizedPath = self::normalizePath($path); foreach (self::HUBS as $hub) { if (preg_match($hub['pattern'], $normalizedPath) === 1) { return true; } } return false; } public static function isExplicitlyExcludedPath(string $path): bool { $normalizedPath = self::normalizePath($path); foreach (self::EXCLUSIONS as $exclusion) { if (preg_match($exclusion['pattern'], $normalizedPath) === 1) { return true; } } return false; } /** * @param array $query * @return array */ public static function cleanQuery(array $query): array { foreach (self::FORBIDDEN_QUERY_KEYS as $key) { unset($query[$key]); } return $query; } /** * @param array $parameters * @return array */ public static function cleanParameters(array $parameters): array { return self::cleanQuery($parameters); } public static function cleanUrl(string $url): string { if (! str_contains($url, '?')) { return $url; } $parts = parse_url($url); if (! is_array($parts)) { return $url; } $query = []; parse_str((string) ($parts['query'] ?? ''), $query); $parts['query'] = http_build_query(self::cleanQuery($query), '', '&', PHP_QUERY_RFC3986); return self::buildUrl($parts); } public static function hasForbiddenQuery(string $url): bool { $parts = parse_url($url); if (! is_array($parts) || ! isset($parts['query'])) { return false; } $query = []; parse_str((string) $parts['query'], $query); foreach (self::FORBIDDEN_QUERY_KEYS as $key) { if (array_key_exists($key, $query)) { return true; } } return false; } public static function isCleanWorkspaceHubEntry(?Request $request = null): bool { $request ??= request(); return self::isWorkspaceHubPath('/'.ltrim((string) $request->path(), '/')) && ! self::requestHasEnvironmentFilterQuery($request); } public static function requestHasEnvironmentFilterQuery(?Request $request = null): bool { $request ??= request(); foreach (self::ENVIRONMENT_FILTER_QUERY_KEYS as $key) { if ($request->query->has($key) && filled($request->query($key))) { return true; } } return false; } private static function normalizePath(string $path): string { $parsedPath = parse_url($path, PHP_URL_PATH); $path = is_string($parsedPath) && $parsedPath !== '' ? $parsedPath : $path; return '/'.trim($path, '/'); } /** * @param array $parts */ private static function buildUrl(array $parts): string { $url = ''; if (isset($parts['scheme'])) { $url .= $parts['scheme'].'://'; } if (isset($parts['user'])) { $url .= $parts['user']; if (isset($parts['pass'])) { $url .= ':'.$parts['pass']; } $url .= '@'; } if (isset($parts['host'])) { $url .= $parts['host']; } if (isset($parts['port'])) { $url .= ':'.$parts['port']; } $url .= $parts['path'] ?? ''; if (($parts['query'] ?? '') !== '') { $url .= '?'.$parts['query']; } if (isset($parts['fragment'])) { $url .= '#'.$parts['fragment']; } return $url; } }