TenantAtlas/specs/108-provider-access-hardening/plan.md
Ahmed Darrazi 12973248e7 feat: provider access hardening (RBAC write gate)
Implements RBAC-based write gating for Intune restore flows, UI affordances, and audit logging; adds tests and specs.
2026-02-23 01:20:28 +01:00

9.9 KiB

Implementation Plan: Provider Access Hardening v1 — Intune Write Gate

Branch: 108-provider-access-hardening | Date: 2026-02-22 | Spec: specs/108-provider-access-hardening/spec.md Input: Feature specification from specs/108-provider-access-hardening/spec.md

Note: This template is filled in by the /speckit.plan command. See .specify/scripts/ for helper scripts.

Summary

Add a defense-in-depth, server-side write gate that blocks all Intune write operations (restore execution + restore assignments) unless tenant RBAC hardening is configured, healthy, and fresh. The gate reads only persisted Tenant RBAC status fields (no synchronous Graph calls), returns stable reason codes, disables Filament write actions with explanations, and fails queued jobs safely before any Graph mutation.

Technical Context

Language/Version: PHP 8.4.x (Laravel 12)
Primary Dependencies: Filament v5 + Livewire v4, Laravel Sail, Microsoft Graph via GraphClientInterface
Storage: PostgreSQL (Sail)
Testing: Pest v4 (PHPUnit 12 underneath)
Target Platform: Web app (Laravel) running in Docker via Sail Project Type: Web application (backend-rendered admin via Filament)
Performance Goals: Gate evaluation is O(1) DB reads; no Graph calls; negligible overhead per write attempt
Constraints: Must be DB-only at evaluation time; stable reason codes; no secrets in logs; no UI render-time HTTP
Scale/Scope: Tenant-scoped; affects existing write start surfaces + queued jobs only (no new Resources/Pages)

Constitution Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

Status: PASS (no violations expected).

  • Inventory-first: clarify what is “last observed” vs snapshots/backups
  • Read/write separation: any writes require preview + confirmation + audit + tests
  • Graph contract path: Graph calls only via GraphClientInterface + config/graph_contracts.php
  • Deterministic capabilities: capability derivation is testable (snapshot/golden tests)
  • RBAC-UX: two planes (/admin vs /system) remain separated; cross-plane is 404; tenant-context routes (/admin/t/{tenant}/...) are tenant-scoped; canonical workspace-context routes under /admin remain tenant-safe; non-member tenant/workspace access is 404; member-but-missing-capability is 403; authorization checks use Gates/Policies + capability registries (no raw strings, no role-string checks)
  • Workspace isolation: non-member workspace access is 404; tenant-plane routes require an established workspace context; workspace context switching is separate from Filament Tenancy
  • RBAC-UX: destructive-like actions require ->requiresConfirmation() and clear warning text
  • RBAC-UX: global search is tenant-scoped; non-members get no hints; inaccessible results are treated as not found (404 semantics)
  • Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked
  • Run observability: long-running/remote/queued work creates/reuses OperationRun; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log; auth handshake exception OPS-EX-AUTH-001 allows synchronous outbound HTTP on /auth/* without OperationRun
  • Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter
  • Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens
  • Badge semantics (BADGE-001): status-like badges use BadgeCatalog / BadgeRenderer; no ad-hoc mappings; new values include tests
  • Filament UI Action Surface Contract: for any new/modified Filament Resource/RelationManager/Page, define Header/Row/Bulk/Empty-State actions, ensure every List/Table has a record inspection affordance (prefer recordUrl() clickable rows; do not render a lone View row action), keep max 2 visible row actions with the rest in “More”, group bulk actions, require confirmations for destructive actions (typed confirmation for large/bulk where applicable), write audit logs for mutations, enforce RBAC via central helpers (non-member 404, member missing capability 403), and ensure CI blocks merges if the contract is violated or not explicitly exempted
  • Filament UI UX-001 (Layout & IA): Create/Edit uses Main/Aside (3-col grid, Main=columnSpan(2), Aside=columnSpan(1)); all fields inside Sections/Cards (no naked inputs); View uses Infolists (not disabled edit forms); status badges use BADGE-001; empty states have specific title + explanation + 1 CTA; max 1 primary + 1 secondary header action; tables provide search/sort/filters for core dimensions; shared layout builders preferred for consistency

Project Structure

Documentation (this feature)

specs/108-provider-access-hardening/
├── plan.md              # This file (/speckit.plan command output)
├── research.md          # Phase 0 output (/speckit.plan command)
├── data-model.md        # Phase 1 output (/speckit.plan command)
├── quickstart.md        # Phase 1 output (/speckit.plan command)
├── contracts/           # Phase 1 output (/speckit.plan command)
└── tasks.md             # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)

Source Code (repository root)

app/
├── Filament/
│   ├── Resources/
│   │   ├── RestoreRunResource.php
│   │   └── TenantResource.php
│   └── Resources/TenantResource/Pages/
│       └── ViewTenant.php
├── Jobs/
│   ├── ExecuteRestoreRunJob.php
│   └── RestoreAssignmentsJob.php
├── Models/
│   ├── Tenant.php
│   ├── OperationRun.php
│   └── AuditLog.php
├── Services/
│   └── Providers/
│       └── ProviderOperationStartGate.php
└── Support/
    └── (badge domains, reason codes, UI enforcement helpers)

tests/
├── Feature/
└── Unit/

Structure Decision: Web application (Laravel) with Filament/Livewire admin panel. Gate logic lives in application services and is invoked from Filament start surfaces and queued jobs.

Complexity Tracking

Fill ONLY if Constitution Check has violations that must be justified

Violation Why Needed Simpler Alternative Rejected Because
[e.g., 4th project] [current need] [why 3 projects insufficient]
[e.g., Repository pattern] [specific problem] [why direct DB access insufficient]

Phase 0 — Outline & Research

Output: specs/108-provider-access-hardening/research.md

Research focus for this feature is mostly decision capture + aligning with existing patterns:

  • Confirm start surfaces and jobs involved (RestoreRun execution + assignments restore)
  • Confirm the existing RBAC setup surface (Tenant view / TenantResource::rbacAction())
  • Confirm existing observability primitives (OperationRun) and audit logging (AuditLog)
  • Decide on behavior for in-flight health check and for gate toggle (bypass + logging)

Phase 1 — Design & Contracts

Outputs:

Design highlights:

  • Introduce a provider-agnostic gate interface with an Intune implementation (IntuneRbacWriteGate).
  • Gate reads only persisted Tenant RBAC fields (rbac_status, rbac_last_checked_at, rbac_status_reason).
  • Gate returns stable reason codes: intune_rbac.not_configured, intune_rbac.unhealthy, intune_rbac.stale.
  • Start surfaces:
    • RestoreRunResource::createRestoreRun() blocks before creating OperationRun + before enqueuing ExecuteRestoreRunJob.
    • Any “rerun/execute” action path in RestoreRunResource blocks similarly before dispatch.
  • Job-level enforcement:
    • ExecuteRestoreRunJob::handle() re-checks gate before calling restore service.
    • RestoreAssignmentsJob::handle() re-checks gate before calling AssignmentRestoreService.
  • UI affordance:
    • Tenant view RBAC infolist section is collapsed into a compact status card with contextual actions.
    • Write-trigger actions render visible but disabled with tooltips when gate would block.

Post-design Constitution Re-check

Status: PASS (design remains DB-only for gate evaluation, does not add Graph calls/contracts, preserves RBAC semantics, and keeps destructive actions confirmed + audited).

Phase 1 — Agent Context Update

Run: .specify/scripts/bash/update-agent-context.sh copilot

Phase 2 — Implementation Planning (for /speckit.tasks)

Implementation tasks will be generated in tasks.md via /speckit.tasks, but the intended breakdown is:

  1. Add gate service + exception (reason codes + message mapping)
  2. Start-surface enforcement (RestoreRun start + rerun + assignments start)
  3. Job-level enforcement (ExecuteRestoreRunJob, RestoreAssignmentsJob)
  4. Tenant view RBAC card + action disabling + CTAs
  5. Audit logging for blocked attempts (use existing AuditLog)
  6. Add/adjust badge semantics for stale in TenantRbacStatus
  7. Pest tests: start surfaces + jobs + UI disabled state + reason codes