TenantAtlas/specs/339-provider-connection-scope-hardening/spec.md
ahmido fcb03d2aee feat: harden provider connection authority resolution (339) (#410)
## Summary
- harden Provider Connection authority so workspace scope comes only from explicit workspace context and record ownership
- require explicit `environment_id` for Provider Connection create flows and remove remembered-environment or Filament-tenant fallback authority
- keep legacy query aliases such as `tenant`, `tenant_id`, and `managed_environment_id` inert for Provider Connection access
- add targeted Spec 339 feature coverage for create authority, workspace authority, and wrong-workspace / legacy-query denial behavior
- include Spec 339 artifacts (`spec.md`, `plan.md`, `tasks.md`) for the hardening slice

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections --filter=ScopeHardening`

## Notes
- no new uncommitted workspace changes were present to commit in this turn; the branch already contained the feature commits
- Livewire v4 compliance unchanged
- Filament provider registration remains in `bootstrap/providers.php`
- no migrations, new assets, or route-family restructures

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #410
2026-05-31 11:59:41 +00:00

16 KiB
Raw Permalink Blame History

Feature Specification: Spec 339 - Provider Connection Scope Hardening

Feature Branch: 339-provider-connection-scope-hardening
Created: 2026-05-31
Status: Draft
Input: Candidate provider-connection-scope-hardening from docs/product/spec-candidates.md + user-provided draft + repo inspection of Provider Connection scope/authority seams.

Spec Candidate Check (mandatory — SPEC-GATE-001)

  • Problem: Provider Connections are credential-adjacent. Their scope and authorization authority must not come from hidden UI/session state (remembered environment) or Filament tenant fallbacks.
  • Today's failure:
    • Provider Connection create authorization can still fall back to remembered environment (WorkspaceContext::lastEnvironmentId) or Filament tenant (Filament::getTenant()), allowing “implicit target environment” when environment_id is missing.
    • Workspace authority can be inferred via Filament tenant when workspace context is missing, which is unsafe for a credential-adjacent surface.
    • Legacy query keys (tenant, tenant_id, managed_environment_id) still exist in the ecosystem and must not regain authority via accidental parsing.
  • User-visible improvement: Operators can trust that Provider Connection list/view/create/edit/actions are scoped only by:
    • explicit workspace context (required), plus
    • explicit environment_id (filter/create), or
    • record ownership (ProviderConnection.workspace_id + ProviderConnection.managed_environment_id) for view/edit/actions.
  • Smallest enterprise-capable version:
    • define and enforce an explicit authority-source contract (allowed vs forbidden),
    • remove hidden fallbacks in ProviderConnectionPolicy and Provider Connection resource query scoping,
    • add targeted tests proving 404 vs 403 semantics and that legacy query aliases cannot grant authority.
  • Explicit non-goals:
    • no provider-neutral target-scope refactor (Spec 281 family),
    • no provider registry / OAuth redesign,
    • no schema changes,
    • no broad link/query cleanup beyond Provider Connections (defer to canonical-link-query-cleanup candidate).
  • Permanent complexity imported: a small set of contract tests + tightened policy/resource guardrails; no new persisted truth or abstraction layer.
  • Why now: Spec 338 tightened the workspace/environment scope contract. Provider Connections remain the main credential-adjacent hub; harden scope authority before adding more provider operations or onboarding variants.
  • Why not local: UI-only fixes do not guarantee consistent authority across create/view/actions. This must be enforced at the policy/resource boundary and guarded by tests.
  • Approval class: Core Enterprise (security / authorization hardening).
  • Red flags triggered: credential-adjacent surface + authorization semantics + cross-surface context drift. Defense: narrow scope, reuse existing WorkspaceContext + capabilities, and forbid new abstraction/persistence.
  • Score: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 2 | Gesamt: 10/12
  • Decision: approve.

Summary

Provider Connections sit at the provider boundary and are credential-adjacent. This spec hardens their authority model so that:

  • Create requires an explicit environment_id (no remembered-environment or Filament-tenant fallback).
  • View/Edit/Actions derive scope from the ProviderConnection record (workspace + managed environment ownership).
  • List filtering uses explicit environment_id only, validated against the current workspace and access scope.
  • Hidden context (topbar remembered environment, Filament tenant context, legacy query aliases) must not grant authority.

This is a narrow P1 security hardening slice following the Spec 338 contract work, with tests that make regressions obvious.

Spec Scope Fields (mandatory)

  • Scope: workspace (workspace-owned hub with optional environment filter)
  • Primary Routes (representative):
    • /admin/provider-connections
    • /admin/provider-connections?environment_id=<id>
    • /admin/provider-connections/create?environment_id=<id>
    • /admin/provider-connections/{record} (view)
    • /admin/provider-connections/{record}/edit
  • Data Ownership: ProviderConnection is workspace-owned and is linked to one ManagedEnvironment via managed_environment_id. No schema changes.
  • RBAC:
    • Non-member / out-of-workspace access: 404 (deny-as-not-found)
    • Member but missing capability: 403
    • Capabilities used by this surface: Capabilities::PROVIDER_VIEW, Capabilities::PROVIDER_MANAGE, and existing dedicated-credential capability variants.

Authority / Scope Contract (Spec 339)

A. Allowed authority sources (canonical)

Create:

  • explicit environment_id query parameter (validated and workspace-owned)
  • explicit environment selection in form state (only if a picker is introduced later)
  • workspace context from WorkspaceContext (required)

View/Edit/Actions:

  • ProviderConnection.workspace_id + ProviderConnection.managed_environment_id
  • record → tenant (ManagedEnvironment) → workspace_id (derived)
  • workspace context from WorkspaceContext for deny-as-not-found semantics and list scoping

B. Forbidden authority sources (must never grant permission)

  • remembered environment (WorkspaceContext::lastEnvironmentId and related helpers)
  • Filament tenant fallback (Filament::getTenant()) as a source of workspace/environment authority
  • legacy query aliases (tenant, tenant_id, managed_environment_id) as authority inputs
  • Filament internal state/query keys as authority (tableFilters[...], etc.)

C. 404 vs 403 semantics (authorization UX contract)

  • Non-member / no workspace context / wrong workspace / out-of-scope record: 404 (deny-as-not-found)
  • Member but missing capability: 403

Required Runtime Decisions

D1 — Create requires explicit environment_id (no hidden fallback)

Decision:

  • Provider Connection create is only valid with explicit environment_id that belongs to the current workspace.
  • Remembered environment and Filament tenant must never substitute for missing environment_id.

Acceptance:

  • /admin/provider-connections/create without environment_id cannot be used to create a connection.
  • Wrong-workspace environment_id denies as 404.
  • Member without PROVIDER_MANAGE denies as 403.

D2 — Workspace authority requires explicit workspace context (no Filament fallback)

Decision:

  • Workspace authority for Provider Connections must come from WorkspaceContext (session workspace), not from Filament tenant fallback.

Acceptance:

  • If workspace context is missing, Provider Connections deny as 404 even if Filament tenant is set.

D3 — Environment filtering is explicit and inert

Decision:

  • environment_id may filter list results but must be validated against current workspace and the users environment access scope.
  • Legacy query keys do not grant access and are not treated as authority.

Acceptance:

  • ?managed_environment_id=..., ?tenant=..., ?tenant_id=... do not grant access or widen results.

UI Surface Impact (mandatory — UI-COV-001)

  • No UI surface impact
  • Existing page changed
  • New page/route added
  • Navigation changed
  • Filament panel/provider surface changed
  • New modal/drawer/wizard/action added
  • New table/form/state added
  • Customer-facing surface changed
  • Dangerous action changed
  • Status/evidence/review presentation changed
  • Workspace/environment context presentation changed

UI/Productization Coverage (UI-COV-001)

  • Route/page/surface: Provider Connections (list, view, create, edit) + credential-adjacent actions (dedicated credential management and provider operations) in ProviderConnectionResource.
  • Current page archetype: workspace admin “Integration/Settings” hub resource (credential-adjacent).
  • Design depth: Domain Pattern Surface (security/authority hardening; minimal UX change expected).
  • Repo-truth level: repo-verified (ProviderConnectionResource, ProviderConnectionPolicy, WorkspaceContext, WorkspaceHubEnvironmentFilter + existing ProviderConnections feature tests).
  • Existing pattern reused: Spec 338 scope contract + existing deny-as-not-found semantics, WorkspaceContext, capabilities, and existing ProviderConnections test families.
  • New pattern required: none (tighten existing seams; avoid new abstraction/persistence).
  • Screenshot required: no (unless implementation changes visible CTA/empty-state messaging; then add one targeted screenshot in the implementation PR).
  • Page audit required: no (existing archetype).
  • Customer-safe review required: no (operator-only surface).
  • Dangerous-action review required: yes (credential-adjacent actions must remain confirmation/authorization/audit-safe and scoped to record ownership).
  • Coverage files to update (in implementation PR):
    • docs/ui-ux-enterprise-audit/route-inventory.md (only if navigation entries/routes change; not expected)
    • docs/ui-ux-enterprise-audit/design-coverage-matrix.md (no new surface; expected no)
    • N/A - no new reachable UI surface added; authority hardening only

Cross-Cutting / Shared Pattern Reuse (mandatory)

  • Cross-cutting feature?: yes
  • Interaction class(es): navigation + scope presentation, authorization semantics, credential-adjacent action gating.
  • Systems touched (expected):
    • apps/platform/app/Filament/Resources/ProviderConnectionResource.php
    • apps/platform/app/Policies/ProviderConnectionPolicy.php
    • apps/platform/app/Support/Workspaces/WorkspaceContext.php
    • apps/platform/app/Support/Navigation/WorkspaceHubEnvironmentFilter.php
    • apps/platform/tests/Feature/ProviderConnections/*
  • Existing pattern(s) to extend: Spec 338 scope/authority contract (explicit environment_id filter, remembered environment is navigation-only, deny-as-not-found for out-of-scope).
  • Allowed deviation and why: none (tighten existing seams; no new surface frameworking).
  • Consistency impact: authority sources must match across list/create/policy/actions; tests must enforce that legacy query aliases and hidden context do not widen authority.
  • Review focus: “credential-adjacent authority comes from explicit input or record ownership only.”

OperationRun UX Impact

  • Touches OperationRun start/completion/link UX?: no
  • N/A: This spec hardens authority sources and authorization semantics. Provider operations that already run via OperationRun must remain scoped to the records owning environment/workspace; no new OperationRun types or start UX changes are introduced.

Provider Boundary / Platform Core Check

  • Shared provider/platform boundary touched?: yes
  • Boundary classification: platform-core authority (workspace/environment scope) + provider-owned identity (provider scope identifiers) must remain separated.
  • Seams affected: Provider Connection authority sources (workspace/environment), policy decisions, and filter/query semantics.
  • Why this does not deepen provider coupling accidentally: The contract uses neutral platform terms (workspace, environment_id, record ownership) and explicitly forbids conflating provider “tenant” identifiers with platform scope authority.
  • Follow-up path: none (follow-up work belongs in canonical-link-query-cleanup and/or Spec 281 family, not in this slice).

Testing / Lane / Runtime Impact (mandatory for runtime behavior changes)

  • Test purpose / classification: Feature (authorization/scope contract).
  • Validation lane(s): fast-feedback (Feature). Browser smoke is optional and only justified if visible UX changes occur.
  • Why this classification and these lanes are sufficient: The risk is scope/authorization drift, which is best proven by targeted policy/resource tests (positive + negative cases) without introducing heavy or browser-wide cost by default.
  • Planned validation commands:
    • cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections --filter=ScopeHardening
    • cd apps/platform && ./vendor/bin/sail pint --dirty --format agent

Acceptance Criteria

  • AC1: Provider Connection create requires explicit environment_id; remembered environment and Filament tenant cannot substitute.
  • AC2: Provider Connection list/view/edit/actions derive scope only from current workspace context + explicit environment_id (filter/create) or from record ownership (view/edit/actions).
  • AC3: Legacy query aliases (tenant, tenant_id, managed_environment_id) do not grant access or widen results.
  • AC4: 404 vs 403 semantics match the repo rules: out-of-scope/non-member is 404; missing capability is 403.
  • AC5: Targeted feature tests are green and make authority regressions obvious.

User Scenarios & Testing (mandatory)

User Story 1 — View Provider Connections safely (Priority: P1)

As an operator in a workspace, I can list and view Provider Connections for environments I am entitled to, and I can optionally filter the list by an explicit environment.

Independent Test: A user with workspace membership and PROVIDER_VIEW can list and view records in-scope; a non-member or wrong-workspace access is 404; a member without capability is 403; legacy query aliases do not widen access.

Acceptance Scenarios:

  1. Given a workspace member with PROVIDER_VIEW on Environment A, When they visit /admin/provider-connections, Then the list contains only connections in environments they can access in the current workspace.
  2. Given the same user, When they visit /admin/provider-connections?environment_id=<A>, Then the list is filtered to Environment A and remains scope-safe.
  3. Given a user without workspace membership, When they hit Provider Connections routes, Then the response is deny-as-not-found (404).
  4. Given a member without PROVIDER_VIEW, When they attempt to list/view, Then the response is 403.
  5. Given any user, When they add tenant, tenant_id, or managed_environment_id query keys, Then those keys do not grant access or change authority behavior.

User Story 2 — Create Provider Connection only with explicit target environment (Priority: P1)

As an operator, I can create a Provider Connection only when I explicitly choose the target environment (by passing environment_id) and I am authorized for that environment.

Independent Test: Create is impossible without explicit environment_id, even if a remembered environment or Filament tenant exists.

Acceptance Scenarios:

  1. Given a member with PROVIDER_MANAGE on Environment A, When they open /admin/provider-connections/create?environment_id=<A>, Then create is allowed and the record is owned by Environment A + the current workspace.
  2. Given a request to /admin/provider-connections/create without environment_id, When the user has a remembered environment, Then create is still denied (no implicit fallback).
  3. Given an environment_id from another workspace, When the user attempts create, Then the response is 404.
  4. Given a member without PROVIDER_MANAGE, When they attempt create for an in-scope environment, Then the response is 403.

User Story 3 — Perform credential-adjacent actions with record-derived authority (Priority: P2)

As an operator, credential-adjacent actions (dedicated credential management, provider operations) execute only when I am authorized for the records owning environment/workspace, independent of remembered environment or Filament tenant state.

Independent Test: A record-bound action uses the records ownership and denies 404/403 correctly.

Acceptance Scenarios:

  1. Given a Provider Connection record owned by Environment A, When a user with access to Environment A triggers an action, Then the action is authorized using record ownership and capability checks.
  2. Given the same record, When a users remembered environment is Environment B, Then authorization still uses record ownership (A), not hidden context.
  3. Given a member without the dedicated credential capability, When they attempt dedicated credential actions, Then the response is 403.

Follow-up spec candidates

  • canonical-link-query-cleanup (broader link/query inventory + alias cleanup after scope contract)
  • environment-resource-context-follow-through (reduce hidden Filament context reliance inside selected environment resources)