TenantAtlas/specs/089-provider-connections-tenantless-ui/spec.md
ahmido fb4de17c63 feat(spec-089): provider connections tenantless UI (#107)
Implements Spec 089: moves Provider Connections to canonical tenantless route under `/admin/provider-connections`, enforces 404/403 semantics (workspace/tenant membership vs capability), adds tenant transparency (tenant column + filter + deep links), adds legacy redirects for old tenant-scoped URLs without leaking Location for 404 cases, and adds regression test coverage (RBAC semantics, filters, UI enforcement tooltips, Microsoft-only MVP scope, navigation placement).

Notes:
- Filament v5 / Livewire v4 compatible.
- Global search remains disabled for Provider Connections.
- Destructive/manage actions require confirmation and are policy-gated.

Tests:
- `vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #107
2026-02-12 16:35:13 +00:00

183 lines
16 KiB
Markdown
Raw 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: Provider Connections (Tenantless UI + Tenant Transparency)
**Feature Branch**: `089-provider-connections-tenantless-ui`
**Created**: 2026-02-12
**Status**: Draft
**Input**: User description: "Provider Connections als workspace-weites Integrations-Asset (tenantless UI) + Tenant-Transparenz"
## Clarifications
### Session 2026-02-12
- Q: Welche Provider sind im MVP-Scope dieser Spec? → A: Nur Microsoft (Graph) im MVP; UI/IA bleibt generisch „Provider Connections“.
- Q: Wie wird „aktiver Tenant-Kontext“ für den Default-Filter bestimmt? → A: TenantContext kommt aus Session/Context-Switcher; `tenant_id` Querystring kann den Default überschreiben.
- Q: Soll „Provider Connections“ in Filament Global Search erscheinen? → A: Nein, Global Search ist für diese Resource deaktiviert.
- Q: Wie soll die Auditability konkret umgesetzt werden? → A: AuditLog + OperationRun.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Workspace-weite Übersicht (Priority: P1)
Als Operator/Admin im Workspace möchte ich Provider Connections zentral unter Settings → Integrations finden und über eine canonical Route ohne Tenant-Parameter aufrufen, damit Integrationen enterprise-typisch auffindbar sind und die UI nicht von tenant-scoped Routes abhängt.
**Why this priority**: Stellt Informationsarchitektur wieder her, reduziert Kontextbrüche und eliminiert fragiles tenant-scoped Routing als Voraussetzung.
**Independent Test**: Aufruf der canonical Liste und Verifikation von Scoping + Default Filter + Tenant-Spalte.
**Acceptance Scenarios**:
1. **Given** ein User ist Mitglied im Workspace und Mitglied in Tenant A, aber nicht in Tenant B, **When** er die Provider-Connections-Liste öffnet, **Then** sieht er ausschließlich Connections aus Tenant A.
2. **Given** ein aktiver Tenant-Kontext ist gesetzt (Tenant A), **When** die Liste geöffnet wird, **Then** ist initial ein Tenant-A Filter aktiv und kann vom User entfernt werden.
3. **Given** ein aktiver Tenant-Kontext ist gesetzt (Tenant A) und der User ruft die Liste mit `?tenant_id=<tenant_external_id_von_TenantB>` auf, **When** die Liste lädt, **Then** ist Tenant B als Filter aktiv (Querystring überschreibt den Context-Default), ohne dass nicht-berechtigte Tenants Metadaten leaken.
4. **Given** ein User ist kein Workspace-Mitglied, **When** er die canonical Provider-Connections-Route aufruft, **Then** erhält er 404 (deny-as-not-found).
---
### User Story 2 - Sicherer Detail-/Edit-Zugriff ohne Secrets (Priority: P2)
Als Tenant-Mitglied möchte ich eine Provider Connection ansehen und (mit entsprechender Berechtigung) verwalten, damit ich Integrationsprobleme diagnostizieren und beheben kann, ohne dass Secrets im Klartext sichtbar werden.
**Why this priority**: Detail-/Edit-Surfaces sind die riskantesten Stellen für Metadaten-Leaks und Secret-Exfiltration.
**Independent Test**: Direkte View/Edit Zugriffe mit unterschiedlichen Membership-/Capability-Kombinationen; UI zeigt keine Klartext-Secrets.
**Acceptance Scenarios**:
1. **Given** ein User ist Workspace-Mitglied aber nicht Mitglied im owning Tenant einer Connection, **When** er die Detailseite direkt aufruft, **Then** erhält er 404.
2. **Given** ein User ist Tenant-Mitglied, aber ihm fehlt die View-Capability, **When** er Liste oder Detailseite aufruft, **Then** erhält er 403.
3. **Given** ein User hat View-, aber nicht Manage-Capability, **When** er die Detailseite öffnet, **Then** sind Manage-Aktionen sichtbar aber deaktiviert (mit Tooltip), und ein server-seitiger Mutationsversuch wird mit 403 abgewiesen.
4. **Given** irgendein berechtigter User sieht Liste/Detail, **When** Credentials/Secrets dargestellt werden, **Then** werden niemals Klartext-Secrets angezeigt und es gibt keine Copy-Aktionen für Secrets.
---
### User Story 3 - Tenant-Detailseite zeigt effektiven Provider-State + Deep Link (Priority: P3)
Als Operator im Tenant-Kontext möchte ich auf der Tenant-Detailseite den effektiven Default-Provider-Connection-Status sehen und über eine CTA direkt zur vorgefilterten Provider-Connections-Liste springen, damit ich Kontext behalte und trotzdem zentral arbeiten kann.
**Why this priority**: Reduziert Kontextbruch und macht den „effective state“ dort sichtbar, wo Operators ihn erwarten.
**Independent Test**: Tenant-Detailseite zeigt Card + CTA; CTA führt zur gefilterten canonical Liste.
**Acceptance Scenarios**:
1. **Given** Tenant A hat eine aufgelöste Default-Connection, **When** der User die Tenant-Detailseite öffnet, **Then** sieht er Display Name + Status/Health + Last Check.
2. **Given** Tenant A hat keine gültige Default-Connection, **When** der User die Tenant-Detailseite öffnet, **Then** sieht er einen klaren „Needs action“-State und eine CTA zur gefilterten Liste.
### Edge Cases
- User ist Workspace-Mitglied, aber in keinem Tenant Mitglied → Liste zeigt 0 Rows (ohne Tenant-Hinweise).
- `tenant_id` Filter verweist auf einen nicht-mitgliedschaftlich berechtigten Tenant → Liste zeigt 0 Rows; direkte Record-Zugriffe bleiben 404.
- Mitgliedschaft wird entzogen → direkte Zugriffe auf vormals sichtbare Records liefern 404.
- Sehr viele Tenants/Connections (MSP) → Scoping bleibt performant (server-seitige, membership-basierte Filterung ohne große In-Memory-ID-Listen).
- Health/Last Error enthält sensitive Inhalte → UI zeigt nur Reason Codes und gekürzte, nicht-sensitive Messages; keine Secrets.
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001 (Canonical route)**: The system MUST expose Provider Connections at a canonical admin route without requiring a tenant route parameter (e.g., `/admin/provider-connections`).
- **FR-002 (Tenant filter)**: The canonical list MUST support optional filtering by owning tenant (e.g., `?tenant_id=<tenant_external_id>`).
- **FR-003 (Tenant transparency)**: List and detail MUST clearly show the owning tenant (name + environment indicator where applicable) and provide a deep link to tenant detail.
- **FR-004 (Context-respecting default filter)**: If a tenant context is active, the list MUST default to filtering by the current tenant. The user MUST be able to remove the filter.
- **FR-004a (Default resolution precedence)**: The default tenant filter MUST be resolved from the active TenantContext (session/context switcher). If a `tenant_id` query parameter is present, it MUST take precedence over the TenantContext-derived default.
- **FR-005 (Zero-leak scoping)**: Index/List MUST only return connections for tenants where the user is a member. The UI MUST not reveal metadata for non-member tenants.
- **FR-006 (Direct access semantics)**: Direct record access (view/edit) MUST be deny-as-not-found (404) when the user is not a member of the owning tenant.
- **FR-007 (Workspace gating)**: If the user is not a workspace member, all Provider Connections routes MUST return 404.
- **FR-008 (Capability-first RBAC)**: Capabilities MUST gate: view (list/detail), manage (create/edit/enable/disable/set-default/credential updates), run (health checks / operation triggers). Missing capability MUST return 403.
- **FR-009 (UI enforcement behavior)**: When missing capability but otherwise eligible (member), the UI SHOULD render actions disabled with a tooltip describing the missing capability (not silently removed), while server-side enforcement stays authoritative.
- **FR-010 (No secrets in UI)**: The UI MUST never display plaintext secrets and MUST NOT provide copy actions for secrets. Non-secret identifiers (e.g., Entra tenant ID) MAY be copyable.
- **FR-011 (List columns minimum)**: The list MUST include at least: Tenant, Provider, Display name, Entra tenant ID, Default indicator, Status badge, Health badge, Last check timestamp, Last error (reason code + truncated message).
- **FR-012 (List filters minimum)**: The list MUST provide filters for: Tenant, Provider, Status, Health, Default-only.
- **FR-013 (Tenant detail card)**: Tenant detail MUST show an “effective provider connection state” for the tenant and provide a CTA to open the canonical Provider Connections list pre-filtered for that tenant.
- **FR-014 (Legacy redirect)**: Legacy tenant-scoped URLs for provider connections MUST behave according to the “Legacy URL Redirect Matrix” (302 redirects only for entitled members; no-leak 404 otherwise) and remain for at least two releases.
- **FR-015 (Scalability requirement)**: Tenant-visibility scoping MUST be performed at query time based on membership relationships and MUST NOT depend on loading large tenant-id lists into memory.
- **FR-016 (MVP provider scope)**: The MVP MUST support Microsoft (Graph) as the only provider. The navigation label and IA remain generic (“Provider Connections”).
- **FR-017 (Global Search)**: Provider Connections MUST NOT appear in global search.
- **FR-018 (Create tenant resolution)**: Create MUST resolve the target tenant to an entitled tenant via `tenant_id` query parameter or the active TenantContext default. If no entitled tenant can be resolved, the create surface MUST behave as not found (404).
### Information Architecture
- Provider Connections MUST be placed under Settings → Integrations → Provider Connections.
- Provider Connections MUST feel “workspace-level” even when a tenant context is active (context affects default filtering, not canonical addressability).
### Scope Boundaries
- In scope: tenantless canonical navigation and routing; tenant transparency in list/detail; context-respecting default filter; deny-as-not-found membership rules; capability-first action gating; legacy redirect behavior.
- Out of scope: shared connections across multiple tenants; redesign of default/override semantics; changing the meaning of “default”; large wizard refactors.
### Assumptions & Dependencies
- A "workspace" membership model exists and can be evaluated for every request.
- A "tenant membership" model exists and is the source of truth for which tenants a user may access.
- Provider Connections already belong to exactly one owning tenant and one workspace.
- A tenant detail surface exists where the “Provider connection” card can be shown.
- The system has, or can represent, a provider identifier for connections (even if only Microsoft exists in the MVP).
- The system has an active TenantContext concept (e.g., chosen via a context switcher) that can be read when rendering admin pages.
### Query Parameter Contract
- **QP-001**: The canonical tenant filter query parameter name is `tenant_id`.
- **QP-002**: The `tenant_id` value is the managed tenants external identifier (the same identifier used in `/admin/tenants/{tenant}` routes), not a database primary key.
### Tenant Transparency Conventions
- **TT-001**: “Environment indicator” means a human-readable label that distinguishes tenant environments when such labeling exists in the workspace (e.g., Production vs Staging). If no such label exists for a tenant, the UI MUST omit the environment indicator (not substitute guesses).
### Default Filter Removal
- **DF-001**: The default tenant filter is a usability default only. Users MAY remove it at any time.
- **DF-002**: Removing the default filter MUST NOT change authorization boundaries: list and detail remain scoped to tenants the user is a member of.
### Authorization Semantics (404 vs 403)
- **AS-001**: Not a workspace member → 404 for list/detail/edit and any action endpoints.
- **AS-002**: Workspace member but not a member of the owning tenant → list returns no rows for that tenant; direct record access → 404.
- **AS-003**: Tenant member but missing required capability → 403 for the protected surface/action.
### Audit & Observability
- **AO-001**: User-initiated actions that change state (set default, enable/disable, credentials update/rotate) MUST be auditable.
- **AO-002**: User-initiated run/health actions MUST be auditable, either via an audit event or a run record that is reachable via a canonical “view run” link.
- **AO-003 (Decision)**: Manage/state-change actions MUST write an AuditLog entry. Run/health actions MUST create an OperationRun (or equivalent run record) that is reachable via a canonical “view run” link.
### Backward Compatibility
- **BC-001**: Redirect MUST preserve intent (tenant filter) and MUST NOT leak tenant names/metadata for non-members.
- **BC-002**: Deprecation window is at least two releases.
### Legacy URL Redirect Matrix
- **LR-001 (Redirect, tenant-managed scope)**: Requests to legacy workspace-managed tenant routes MUST redirect (302) to the canonical route and preserve intent via the tenant filter:
- `/admin/tenants/{tenant_external_id}/provider-connections``/admin/provider-connections?tenant_id={tenant_external_id}`
- `/admin/tenants/{tenant_external_id}/provider-connections/create``/admin/provider-connections/create?tenant_id={tenant_external_id}`
- `/admin/tenants/{tenant_external_id}/provider-connections/{record}/edit``/admin/provider-connections/{record}/edit?tenant_id={tenant_external_id}`
- **LR-002 (No-leak)**: For non-workspace members or users who are not members of the target tenant, legacy URLs MUST behave as not found (404) and MUST NOT redirect.
- **LR-003 (Explicit exclusion)**: The previously removed tenant-panel management route shape `/admin/t/{tenant_external_id}/provider-connections` remains not found (404) and is not part of the redirect surface.
## UI Action Matrix *(mandatory when Filament is changed)*
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|---|---|---|---|---|---|---|---|---|---|---|
| Provider Connections (List) | Admin → Settings → Integrations → Provider Connections | Create (manage) | Tenant deep link + View action | View (view), Edit (manage) | None (not required) | Create (manage) | n/a | n/a | Yes | Default tenant filter applies when tenant context active |
| Provider Connection (View) | Provider Connections → View | Enable/Disable (manage), Set default (manage), Health check (run), Credential update (manage) | n/a | Edit (manage) | None | n/a | Same as header actions | n/a | Yes | Non-membership is 404; missing capability is 403; destructive-like actions require confirmation |
| Provider Connection (Create/Edit) | Provider Connections → Create/Edit | None | n/a | None | None | n/a | n/a | Save (manage), Cancel | Yes | Secrets never displayed; credential updates require confirmation |
| Tenant detail “Provider connection” card | Tenant detail page | Open Provider Connections (view) | n/a | None | None | Optional: Create connection (manage) | n/a | n/a | No | CTA links to canonical list filtered by tenant |
### Key Entities *(include if feature involves data)*
- **Workspace**: A membership-gated scope that owns managed tenants and integrations.
- **Managed Tenant**: A tenant within a workspace; users are members of specific tenants.
- **Provider Connection**: An integration record owned by exactly one managed tenant and one workspace; includes status/health metadata and non-secret identifiers.
- **Capabilities**: Named permissions that gate view/manage/run behavior; UI enforcement reflects missing capabilities.
- **Audit Event / Run Record**: Captures sensitive user-initiated actions for later review.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001 (IA discoverability)**: A workspace admin can reach Provider Connections from the admin sidebar in ≤ 2 clicks (Settings → Integrations → Provider Connections).
- **SC-002 (No metadata leaks)**: For non-members of a tenant, 100% of direct access attempts to that tenants Provider Connection detail/edit routes return 404 and do not reveal tenant/provider/status/health metadata.
- **SC-003 (RBAC correctness)**: For tenant members lacking a required capability, 100% of protected endpoints return 403; the UI consistently shows disabled actions with an explanatory tooltip.
- **SC-004 (Context-respecting UX)**: From a tenant detail page, the “Open Provider Connections” CTA lands on a pre-filtered list for that tenant with a first-attempt success rate ≥ 95% in acceptance testing.