## 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
236 lines
16 KiB
Markdown
236 lines
16 KiB
Markdown
# 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 user’s 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 record’s 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 record’s owning environment/workspace, independent of remembered environment or Filament tenant state.
|
||
|
||
**Independent Test**: A record-bound action uses the record’s 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 user’s 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)
|