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

236 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
- [x] 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
- [x] Dangerous action changed
- [ ] Status/evidence/review presentation changed
- [x] 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`)
- [x] `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)