TenantAtlas/.specify/spec.md
2025-12-14 20:23:18 +01:00

35 KiB
Raw Blame History

Feature Specification: TenantPilot v1

Feature Branch: tenantpilot-v1
Created: 2025-12-10
Status: Draft
Input: TenantPilot v1 scope covering Intune configuration inventory (config, compliance, scripts, apps, conditional access, endpoint security, enrollment/autopilot, RBAC), backup, version history, and defensive restore for Intune administrators.

Scope

scope:
  description: "v1 muss folgende Intune-Objekttypen inventarisieren, sichern und  je nach Risikoklasse  wiederherstellen können."
  supported_types:
    - key: deviceConfiguration
      name: "Device Configuration"
      graph_resource: "deviceManagement/deviceConfigurations"
      notes: "Inklusive Custom OMA-URI, Administrative Templates und Settings Catalog."

    - key: deviceCompliancePolicy
      name: "Device Compliance"
      graph_resource: "deviceManagement/deviceCompliancePolicies"

    - key: appProtectionPolicy
      name: "App Protection (MAM)"
      graph_resource: "deviceAppManagement/managedAppPolicies"
      notes: "iOS und Android Managed App Protection."

    - key: conditionalAccessPolicy
      name: "Conditional Access"
      graph_resource: "identity/conditionalAccess/policies"
      notes: "Kritisch für Sicherheit. Policy.Read.All/Policy.ReadWrite.All nötig; v1: Restore nur mit starker Preview."

    - key: deviceManagementScript
      name: "PowerShell Scripts"
      graph_resource: "deviceManagement/deviceManagementScripts"
      notes: "scriptContent wird beim Backup base64-decoded gespeichert und beim Restore wieder encoded (vgl. FR-020)."

    - key: enrollmentRestriction
      name: "Enrollment Restrictions"
      graph_resource: "deviceManagement/deviceEnrollmentConfigurations"

    - key: windowsAutopilotDeploymentProfile
      name: "Windows Autopilot Profiles"
      graph_resource: "deviceManagement/windowsAutopilotDeploymentProfiles"

    - key: windowsEnrollmentStatusPage
      name: "Enrollment Status Page (ESP)"
      graph_resource: "deviceManagement/deviceEnrollmentConfigurations"
      filter: "odata.type eq '#microsoft.graph.windows10EnrollmentCompletionPageConfiguration'"

    - key: endpointSecurityIntent
      name: "Endpoint Security Intents"
      graph_resource: "deviceManagement/intents"
      notes: "Account Protection, Disk Encryption etc.; Zuordnung über bekannte Templates."

    - key: mobileApp
      name: "Applications (Metadata only)"
      graph_resource: "deviceAppManagement/mobileApps"
      notes: "Backup nur von Metadaten/Zuweisungen (kein Binary-Download in v1)."
    
    - key: settingsCatalogPolicy
      name: "Settings Catalog Policy"
      graph_resource: "deviceManagement/configurationPolicies"
      notes: "Intune Settings Catalog Policies liegen NICHT unter deviceConfigurations, sondern unter configurationPolicies. v1 behandelt sie als eigenen Typ."

  restore_matrix:
    deviceConfiguration:
      backup: full
      restore: enabled
      risk: medium
      notes: "Standard-Case für Backup+Restore; starke Preview/Audit Pflicht."

    deviceCompliancePolicy:
      backup: full
      restore: enabled
      risk: medium
      notes: "Compliance-Änderungen können Zugriff beeinflussen, aber sind gut verständlich."

    appProtectionPolicy:
      backup: full
      restore: enabled
      risk: medium-high
      notes: "MAM-Änderungen wirken auf Datenzugriff in Apps; Preview und Diff wichtig."

    conditionalAccessPolicy:
      backup: full
      restore: preview-only
      risk: high
      notes: "Hohe Ausfallgefahr. v1: Backup, Versioning, Diff + ausführliche Preview; Restore nur manuell anhand Preview."

    deviceManagementScript:
      backup: full
      restore: enabled
      risk: medium
      notes: "Script-Inhalt und Einstellungen werden gesichert; Decode/Encode beachten."

    enrollmentRestriction:
      backup: full
      restore: preview-only
      risk: high
      notes: "Kann Enrollment blockieren; v1 eher nur Preview + manuelle Umsetzung."

    windowsAutopilotDeploymentProfile:
      backup: full
      restore: enabled
      risk: medium-high
      notes: "Provisioning-kritisch; Preview + Audit, aber automatisierbar."

    windowsEnrollmentStatusPage:
      backup: full
      restore: enabled
      risk: medium
      notes: "ESP beeinflusst OOBE UX; Änderungen klar sichtbar."

    endpointSecurityIntent:
      backup: full
      restore: enabled
      risk: high
      notes: "Security-relevante Einstellungen (z. B. Credential Guard); Preview + klare Konflikt-Warnungen nötig."
    
    settingsCatalogPolicy:
      backup: full
      restore: enableds
      risk: medium
      notes: "Settings Catalog Policies sind Standard-Config-Policies (Settings Catalog). Preview/Audit Pflicht; Restore automatisierbar."

    mobileApp:
      backup: metadata-only
      restore: enabled
      risk: low-medium
      notes: "Nur Metadaten/Zuweisungen; kein Binary; Restore setzt Konfigurationen/Zuweisungen wieder."

User Scenarios & Testing (mandatory)

User Story 1 - Policy inventory listing (Priority: P1)

Admin can view supported Intune object types (as defined in the scope) with normalized metadata for selection.

Why this priority: Inventory is the entry point for backup/version flows. Without it, no downstream workflows are usable.

Independent Test: From Filament, navigate to Policies; verify supported types render with identifiers, type/category, platform metadata, and tenant scoping.

Acceptance Scenarios:

  1. Given an authenticated admin, When they open the Policies list, Then they see supported object types with identifiers, type/category, platform, and last-updated metadata.
  2. Given filtering by type/category, When the admin selects a type, Then only matching objects appear and the view remains tenant-scoped.
  3. Given Settings Catalog Policies exist in Intune, When the admin opens the Policies list and syncs, Then Settings Catalog Policies are listed as type Settings Catalog Policy (settingsCatalogPolicy) and are not mixed into Device Configuration.

User Story 1b - Policy detail shows readable settings (Priority: P1)

Admin can open a policy detail page and see the effective Intune settings in a readable, normalized way (not raw JSON dumps).

Independent Test: From Filament, open a policy detail view; verify a "Settings" section renders normalized key/value pairs (or tables for special cases) derived from the latest snapshot.

Acceptance Scenarios:

  1. Given a policy with at least one captured snapshot, When the admin opens the policy detail view, Then they see a "Settings" section rendering the policy configuration in a readable format (grouped/labeled).
  2. Given the snapshot contains nested structures or list-based settings (e.g., OMA-URI / Settings Catalog), When the admin views settings, Then values are flattened/grouped or rendered as tables, and irrelevant metadata keys are hidden.

User Story 2 - Backup creation and browsing (Priority: P1)

Admin creates backup sets containing multiple objects (config, compliance, scripts, apps, CA, etc.) with immutable snapshots and can browse backup details in Filament.

Why this priority: Backups provide safety and enable restore; immutability and audit are foundational.

Independent Test: Initiate a backup set selecting multiple objects; confirm immutable JSONB snapshots persisted, audit log written, and Filament shows backup detail and items.

Acceptance Scenarios:

  1. Given selected objects from different categories, When the admin creates a backup set, Then backup items store immutable payload snapshots (full or metadata-only as per the restore matrix) with identifiers and types.

  2. Given a completed backup set, When the admin opens its detail page, Then all items and metadata display along with the audit record of creation.

  3. Given mehrere Backup-Sets existieren, When der Admin ein Backup-Set auswählen oder ansehen möchte, Then sieht er für jedes Set:

    • einen sprechenden Namen (nicht nur Timestamp),
    • das Erstellungsdatum,
    • die Anzahl der enthaltenen Items,
    • und optional eine kurze Beschreibung, damit er das Set sinnvoll unterscheiden kann.

User Story 3 - Version history and diff (Priority: P1)

Admin can capture versions for any supported object, view timelines, and compare any two versions with meaningful diffs.

Why this priority: Version visibility and diffs enable rollback readiness and change comprehension.

Independent Test: Create multiple versions for a given object; verify timeline ordering, version metadata, and diff output (human summary + JSON diff where feasible) between any two versions.

Acceptance Scenarios:

  1. Given an admin triggers version capture for an object, When the version is saved, Then an immutable snapshot and metadata (actor, time, type, tenant) are recorded.
  2. Given two versions of the same object, When the admin requests a comparison, Then the UI shows a human-readable summary and structured JSON diff where available.
  3. Given a saved policy version, When the admin opens the version detail page, Then the snapshot is displayed as pretty-printed JSON and, where possible, as normalized settings (not as an unreadable serialized array/string).

User Story 4 - Restore with preview and confirmation (Priority: P1)

Admin can run a restore from a backup set with preview/dry-run, selective restore, clear warnings, and required confirmation before execution.

Why this priority: Restore is high-risk; safety features are mandatory for production readiness.

Independent Test: Start a restore from a backup set in preview; view change summary and warnings; select items; confirm execution; verify audit logs and outcomes recorded (success/failure/partial).

Acceptance Scenarios:

  1. Given a backup set, When the admin initiates a restore in preview mode, Then the system shows a change summary with selectable items and conflict warnings.

  2. Given selected items and explicit confirmation, When execution proceeds, Then applied changes are tenant-scoped and audit logs record start, result, and any failures.

  3. Given mehrere Backup-Sets existieren, When der Admin einen Restore Run erstellt, Then zeigt die Auswahl für das "Backup set" mindestens:

    • den Backup-Namen,
    • das Erstellungsdatum,
    • die Anzahl der Items, damit der Admin das richtige Backup-Set sicher auswählen kann.
  4. Given ein Restore Run wurde erstellt, When der Admin die Detailseite des Restore Runs öffnet, Then sieht er, welche Policies/Items in diesem Run enthalten sind (z. B. Liste der Policies mit Name/Typ/Plattform).


User Story 5 - Operational readiness and environments (Priority: P2)

Local development uses Sail; deployments target Dokploy staging then production with clear validation steps.

Why this priority: Ensures reproducible local setup and safe promotion to production.

Independent Test: Run the app locally via Sail; validate migrations on staging before production; confirm required env vars and queues/workers are documented.

User Story 6 - Berechtigungsübersicht & Health-Status (Priority: P1)

Als Admin möchte ich für jeden Tenant sehen, welche Microsoft Graph-Berechtigungen erforderlich sind, welche bereits erteilt wurden und welche fehlen, damit ich sicherstellen kann, dass alle Funktionen von TenantPilot sicher und vollständig arbeiten.

Why this priority: Jede neue Funktion kann zusätzliche Berechtigungen benötigen. Ohne transparente Übersicht und Abgleich besteht das Risiko, dass Features still kaputt sind oder unsicher laufen.

Acceptance Scenarios:

  1. Given ein Tenant ist in TenantPilot hinterlegt, When der Admin die Tenant-Detailseite öffnet, Then sieht er eine Liste aller erforderlichen Berechtigungen mit Status (z. B. OK, fehlt).

  2. Given neue Funktionen wurden eingeführt, die zusätzliche Berechtigungen benötigen und diese wurden in der zentralen Permissions-Liste hinzugefügt, When der Admin die Tenant-Detailseite öffnet, Then erscheinen die neuen Berechtigungen automatisch in der Übersicht und fehlende Berechtigungen werden klar als fehlend markiert.

  3. Given der Admin klickt auf "Verify configuration", When TenantPilot einen Graph-Twestcall und/oder das Permission-Setup prüft, Then wird der Status der Berechtigungen aktualisiert (OK/fehlt/Fehler) und es wird ein Audit-Eintrag erstellt.

  4. Given ein Tenant hat fehlende kritische Berechtigungen, When andere Features (Policy-Sync, Backup, Restore) diesen Tenant verwenden, Then kann TenantPilot dem Admin entsprechende Warnungen anzeigen oder die Funktion mit einem klaren Fehler abbrechen.

Acceptance Scenarios:

  1. Given a fresh checkout, When Sail commands run (./vendor/bin/sail up -d, ./vendor/bin/sail artisan migrate), Then the app boots with PostgreSQL and Filament admin available.
  2. Given a pending release, When migrations and restore flows are validated on staging, Then production deployment proceeds with documented steps and environment parity.

User Story 8 Graph Contract Registry & Drift Guard (Priority: P1)

Admin soll sich darauf verlassen können, dass Backup/Restore/Preview nicht wegen Graph-Shape-Details (derived @odata.type, verbotene $expand/$select, Property-Abweichungen) “random” bricht.

Acceptance Scenarios: 1. Given ein Backup enthält @odata.type = #microsoft.graph.windows10CompliancePolicy, When Preview/Restore läuft, Then wird das als gültiger deviceCompliancePolicy-Family Typ behandelt (kein odata_mismatch), und der korrekte Endpoint/Method wird genutzt. 2. Given ein Endpoint erlaubt bestimmte Expands/Selects nicht, When TenantPilot Requests baut, Then werden nur “allowed capabilities” verwendet (kein 400 durch OData parsing). 3. Given Microsoft/Intune ändert Shape/Capabilities, When graph:contract:check läuft, Then schlägt der Check kontrolliert fehl und zeigt welcher Contract angepasst werden muss (statt dass Prod-Flows brechen).

  1. Neue Functional Requirements (FR-03x) ergänzen

Beispiel, passend zu deinem Stil: • FR-031: System MUST maintain a central Graph Contract Registry per supported type/endpoint (resource path, allowed $select, allowed $expand, “type family” / allowed @odata.type values, create/update methods). • FR-032: Restore/Preview MUST treat derived @odata.type values as compatible within a declared type family (e.g. compliance policy family), and MUST NOT hard-fail on base-vs-derived mismatches. • FR-033: System MUST provide a verification command (e.g. php artisan graph:contract:check) that validates registry assumptions against live Graph behavior (at least via canary calls / lightweight probes), logging actionable diffs. • FR-034: When Graph returns capability errors (OData select/expand, unsupported features), system MUST downgrade to a safe fallback strategy (e.g. “no expand, extra fetches”) and MUST record a warning/audit entry.

(Du kannst FR-033 “live check” auch optional machen für prod, aber mindestens in CI/Staging wertvoll.)

  1. Implementation Notes / Data Artefacts ergänzen

Ein kleines, versioniertes Artefakt einführen, z. B.: • config/graph_contracts.php oder .specify/contracts/graph.yaml

Darin pro Objekt-Typ: • resource (collection + single-item path) • type_family (Liste erlaubter @odata.type) • allowed_select / allowed_expand • member_hydration_strategy (z. B. “property array” vs “subresource” vs “not available”) • create_method / update_method / id_field

Das verhindert “Wissens-Leaks” quer durch Services.

Edge Cases

  • Graph permissions missing or expired, causing policy fetch/restore failures with clear error mapping and audit entries.
  • Large policy payloads or many items in a backup set; ensure JSONB storage and pagination handle load without timeouts.
  • Restore conflicts when target tenant already has newer versions; preview must surface warnings and allow skip.
  • Partial restore failures; audits must capture per-item outcomes and surface retry guidance.
  • Diff generation for incompatible or malformed payloads should fail gracefully with admin-facing messaging.
  • Retention/size concerns for snapshots; document defaults and guard against unbounded growth.
  • Snapshots stored as serialized strings or array-only dumps (keys lost) must be detected; UI should show a clear warning and fall back to raw display.
  • Policies whose @odata.type does not match the expected platform/type mapping should be flagged to prevent wrong restore previews (e.g., stored as Windows but snapshot indicates Android).

Requirements (mandatory)

Functional Requirements

  • FR-001: System MUST list all Intune objects defined in the scope.supported_types section with normalized metadata and tenant scoping for selection.

  • FR-002: System MUST allow admins to create backup sets containing multiple objects (configuration, compliance, scripts, apps, conditional access, etc.) with immutable JSONB payload snapshots.

  • FR-003: Backup creation MUST log audit events including actor, timestamp, tenant, items, and outcome.

  • FR-004: System MUST capture policy versions on demand and present per-policy timelines.

  • FR-005: Users MUST be able to diff any two versions with a human-readable summary and structured JSON diff where feasible.

  • FR-006: Restore MUST support preview/dry-run, selective item restore, and explicit confirmation before applying changes, within the per-type restore level defined in scope.restore_matrix.

  • FR-007: Restore execution MUST produce audit logs covering success, failure, and partial outcomes.

  • FR-008: Graph integration MUST route through a dedicated abstraction layer with standardized error mapping, safe retries, and high-level logging without secrets.

  • FR-009: All policy, version, backup, and restore data MUST be tenant-aware; queries enforce tenant isolation.

  • FR-010: Application MUST run locally via Laravel Sail with PostgreSQL and provide Filament admin flows.

  • FR-011: Deployments MUST target Dokploy staging before production with documented migration and worker implications.

  • FR-012: Tests MUST cover backup composition rules, version immutability, audit events, and Filament backup/restore flows (with Graph boundaries mocked).

  • FR-013: Raw policy snapshots and backup payloads MUST be stored as JSONB with indexes justified by query needs (e.g., FK and time-based; GIN when filters require).

  • FR-014: UI MUST provide clear warnings for potential restore conflicts and require confirmation for destructive operations; for types with restore: preview-only in scope.restore_matrix no direct apply action MAY be offered.

  • FR-015: Admins MUST be able to safely delete (archive) backup sets that are no longer needed. Deletion is implemented as soft-delete with audit logging, and backup sets referenced by completed restore runs cannot be removed.

  • FR-016: Admins MUST be able to delete individual policy versions for housekeeping. Deletion is implemented as soft-delete with audit logging.

  • FR-017: Admins MUST be able to deactivate (soft-delete) a tenant. Deactivated tenants:

    • do not appear in default lists,
    • cannot be used for new sync/backup/restore operations,
    • keep their historical data and audit logs for traceability.
  • FR-018: Admins MAY soft-delete restore runs to keep the UI clean; underlying backup and policy data remains untouched.

  • FR-019: The system MUST normalize different payload structures for display via a PolicyNormalizer (or equivalent): OMA-URI/custom policies as path/value tables, Settings Catalog policies as flattened structures, and standard objects as key-value views, aligned with scope.supported_types.

  • FR-019a: Policy detail views MUST display a "Settings" section derived from the latest available snapshot (using the normalizer output when available).

  • FR-019b: Policy version detail views MUST render snapshots as pretty-printed JSON (monospace, copyable) and SHOULD also render normalized settings via the same normalizer.

  • FR-020: For PowerShell script objects (deviceManagementScript in scope.supported_types), the scriptContent MUST be base64-decoded when stored in backups/versions for readability/diffing and encoded again when sent back to Graph during restore.

  • FR-021: Restore behavior MUST follow the per-type configuration in scope.restore_matrix: backup determines full vs metadata-only snapshots; restore determines whether automated restore is enabled or preview-only; risk informs warning/confirmation UX.

  • FR-022: For high-risk types with restore: preview-only in scope.restore_matrix (e.g., conditionalAccessPolicy, enrollmentRestriction), TenantPilot MUST provide full backups, version history, and diffs plus detailed restore previews, but MUST NOT expose direct Graph apply actions; restore is manual, guided by the preview.

Key Entities (include if feature involves data)

  • tenants: Represents the deployment tenant context; referenced by all scoped data.
  • policies: Normalized metadata for supported Intune policies.
  • policy_versions: Immutable snapshots with metadata (actor, timestamp, tenant, policy type).
  • backup_sets: Group of backup items with creator, timestamp, and tenant context.
  • backup_items: Individual policy snapshots within a backup set (immutable JSONB payload + identifiers).
  • restore_runs: Execution records for restores, including preview/actual flags and outcomes.
  • audit_logs: Audit trail entries for backups, restores, version captures, and significant Graph actions.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: Admin can create a backup set selecting multiple policies and view immutable backup items with audit logs in Filament.
  • SC-002: Policy version history timeline is available per policy and supports comparing any two versions with summary and JSON diff outputs.
  • SC-003: Restore preview shows change summaries and conflict warnings; execution requires explicit confirmation and produces audit logs for all outcomes.
  • SC-004: Core flows run locally via Sail; staging validation of migrations and restore paths completes before production deployments.
  • SC-005: Automated tests covering backup composition, version immutability, audit logging, and Filament backup/restore flows pass via ./vendor/bin/sail artisan test.

Technical Story Enforce Single Current Tenant ("Highlander Principle")

Context

Aktuell können mehrere Tenants status = active sein. Graph-Operationen (Policy Sync, Backup, Restore) wählen den Kontext über Heuristiken (findOrCreateDefault, local-tenant), was zu falschen Tenants und Fehlern führt.

Goal

Es soll immer genau einen klar definierten "current" Tenant geben, über den alle Graph-Operationen laufen. Die Auswahl dieses Tenants ist explizit und transparent (UI + Env), nicht implizit.

Requirements

  • Es gibt ein Flag is_current in tenants, das den aktuell verwendeten Kontext markiert.

  • Die Datenbank erzwingt per partiellem Unique Index, dass höchstens ein nicht-gelöschter Tenant is_current = true haben kann.

  • Tenant::current() liefert:

    • falls INTUNE_TENANT_ID gesetzt ist, genau diesen Tenant (Fehler, wenn er nicht existiert oder deaktiviert ist),
    • sonst den Tenant mit is_current = true und status = active.
    • falls keiner gefunden wird, eine klare Exception (“No current tenant selected”); es werden keine Dummy-Tenants erzeugt.
  • In der Tenant-Verwaltung gibt es eine Action "Make current", die:

    • in einer Transaktion alle anderen Tenants auf is_current = false setzt und den gewählten Tenant auf is_current = true,
    • nur für aktive Tenants verfügbar ist.
  • Der frühere Placeholder local-tenant darf nicht mehr als Graph-Kontext genutzt werden; sobald ein echter Tenant existiert, wird er archiviert und ist nie is_current.

  • Alle Graph-basierten Funktionen (Policy Sync, Backup, Restore) verwenden konsistent Tenant::current() oder einen explizit übergebenen Tenant.

    Tenant-level actions such as "Admin consent" and "Verify configuration" MUST be exposed on the tenant detail view (and/or row actions), not as a global button without explicit tenant context.

UX Guideline Table Actions / Dropdowns

  • Tabellen in Filament mit mehr als zwei Zeilen-Aktionen (z.B. View, Edit, Admin consent, Verify, Deactivate, Force delete) MÜSSEN ihre Aktionen in einem kompakten Dropdown / ActionGroup bündeln, statt alle Buttons nebeneinander anzuzeigen.
  • Ausnahmen: besonders häufige, nicht-destruktive Aktionen (z.B. "View") dürfen weiterhin als einzelner Button sichtbar bleiben; alle weiteren Aktionen (z.B. Admin-Aktionen, Housekeeping) sollen im Dropdown liegen.
  • Ziel: die Tabellen bleiben übersichtlich, Spaltenbreite wird begrenzt, und Admins bekommen eine konsistente "⋯"-Interaktion für erweiterte Aktionen.

User Story 7 Intune RBAC Onboarding Wizard (Delegated Admin Login) (Priority: P1)

Problem / Context

TenantPilot arbeitet primär app-only (Client Credentials) gegen Microsoft Graph.
Für viele Intune-Objekte reicht „Graph App Permissions + Admin Consent“ allein nicht aus: Intune kann zusätzlich über Intune RBAC blockieren, wenn der Service Principal (Enterprise App) keine passende Intune Role Assignment inkl. Scope hat. Das äußert sich typischerweise als 403 mit „Application is not authorized to perform this operation“.

Dieses Setup ist ein Bootstrap-Problem:

  • Ohne RBAC-Zuweisung sind Intune Reads/Writes blockiert.
  • Ohne ausreichende Rechte kann TenantPilot die RBAC-Zuweisung nicht „self-service“ per app-only herstellen.

Ziel: TenantPilot bietet pro Tenant einen Onboarding-Wizard, bei dem ein Admin sich interaktiv (delegated) anmeldet, und TenantPilot automatisiert (idempotent) die erforderliche Intune-RBAC-Konfiguration für die konfigurierte Enterprise App herstellt. Danach funktionieren Policy Sync / Backup / Restore (gemäß Restore Matrix) zuverlässig.


User Value

  • Admins können RBAC-Probleme direkt in TenantPilot beheben (kein “Portal-Rätselraten”).
  • Klarer, auditierter Ablauf (wer hat wann welche Rechte/Sopes gesetzt).
  • Minimiert Ausfälle bei Policy-Sync/Backup/Restore und reduziert Support-Aufwand.

In Scope (v1)

  • Wizard in Filament auf der Tenant-Detailseite (tenant-scoped).
  • Delegated Admin Login (interaktiv).
  • Idempotente Ausführung:
    • Service Principal (zu tenant.app_client_id) auflösen
    • RBAC-Membership via Security Group (recommended) herstellen
    • Intune Role Assignment erstellen/aktualisieren (Rolle + Scope)
    • Abschließender Verify-Run (Health/Permissions aktualisieren)
  • Vollständige Audit-Logs pro Step.

Out of Scope (v1)

  • Vollautomatisches “Self-heal” ohne Admin-Interaktion.
  • Zeitgesteuerte Jobs, die RBAC-Rechte vergeben (ohne explizite Admin-Aktion).
  • Unterstützung mehrerer paralleler RBAC-Profile pro Tenant (nur ein “recommended setup”).

UX / Entry Points

Entry Point: Tenant Detail (Filament)

Auf der TenantResource Detailseite im Action-Dropdown:

  • Setup Intune RBAC (Wizard)
  • Admin consent
  • Verify configuration

Visibility rules:

  • Nur für status=active Tenants.
  • Nur wenn app_client_id gesetzt ist.
  • Optional: Badge/Hint “RBAC missing” aus Health-Check.

Copy/Help:

  • Kurze Erklärung: “Graph Permissions ≠ Intune RBAC”.
  • Hinweis auf Least Privilege.
  • Klarer Hinweis, dass Änderungen tenantweit wirken (je nach Scope).

Wizard Flow

Step 1 — Configuration (Role / Scope / Group)

Inputs:

  • Role (Dropdown):
    • Default: Policy and Profile Manager (Least Privilege für Policy/Config-Workflows)
    • Optional: Intune Administrator (mit Warnung)
  • Scope (Dropdown):
    • Default: Global / All devices (wenn verfügbar)
    • Optional: Auswahl einer Scope Group / Device Group (falls euer Modell das nutzt)
  • Group Mode:
    • Default: Use Security Group (recommended)
    • Options:
      • Create new group (Default-Name: TenantPilot-Intune-RBAC)
      • Use existing group (Picker)

UI Requirements:

  • “Review screen” zeigt genau, was erstellt/geändert wird (Role, Scope, Group).

Step 2 — Delegated Admin Login

  • Admin führt interaktiven Login durch (delegated).
  • Wizard zeigt klar:
    • welcher Tenant
    • welche App (Client ID / Display Name, sofern auflösbar)
    • dass nur kurzzeitig ein User-Token genutzt wird

Security rule (mandatory):

  • Delegated Access Tokens werden nicht persistiert (keine Speicherung in DB/Cache).
  • Tokens existieren nur im Request-Kontext / Session und werden nach Abschluss verworfen.

Step 3 — Execute Setup (Idempotent + Safe)

Wizard führt folgenden Ablauf aus (alle Operationen tenant-scoped, über Graph-Abstraktion, mit Error-Mapping):

  1. Resolve Service Principal
  • Auflösen des Service Principals zur tenant.app_client_id.
  • Wenn nicht gefunden:
    • Wizard stoppt mit Hinweis: “Enterprise App ist im Tenant nicht vorhanden. Bitte zuerst Admin Consent durchführen.”
    • Audit log: tenant.rbac.setup.failed (reason: sp_not_found)
  1. Ensure Security Group
  • Falls “Create new group”:
    • Security Group erstellen (securityEnabled=true, mailEnabled=false).
    • Wenn bereits vorhanden (gleiches displayName): wiederverwenden (oder per gespeicherter rbac_group_id).
  • Falls “Use existing group”:
    • Validieren: securityEnabled=true.
  • Ergebnis-IDs werden gespeichert:
    • tenants.rbac_group_id (neu, optional)
    • tenants.rbac_group_name (optional, nur für UX)
  1. Ensure Membership (SP ∈ Group)
  • Service Principal als Member hinzufügen, wenn nicht vorhanden.
  • Konflikte (already exists) müssen als OK behandelt werden.
  1. Ensure Intune Role Assignment
  • Suche nach existierendem Role Assignment, das:
    • die gewünschte RoleDefinition referenziert
    • die Group als Member enthält
    • den gewünschten Scope abdeckt
  • Wenn vorhanden: Patch/Update (z. B. Scope ergänzen)
  • Wenn nicht vorhanden: Create Role Assignment
  1. Post-Verify (mandatory)
  • Direkt nach Setup:
    • Verify configuration ausführen (inkl. Permission-Matrix Update)
    • Zusätzlich 12 “Canary Calls” gegen Intune-Endpunkte, die für v1 kritisch sind (Read-Only reicht).
  • Ergebnisse werden in Tenant-Health gespeichert (app_status, permissions health).

Execution mode (v1):

  • Wizard führt die Steps synchron aus (kein Queue-Job), um Token-Probleme zu vermeiden.
  • Timeouts: klare Fehlermeldung + Audit.

Step 4 — Summary

  • Wizard zeigt:
    • Group (Name + ObjectId)
    • Role (Name)
    • Scope (global / group id)
    • RoleAssignmentId (falls verfügbar)
    • Verify result (OK / Partial / Failed)
  • CTA: “Retry policy sync”

Data Model Additions (minimal)

Erweiterung tenants (optional aber empfohlen, für Transparenz & Idempotenz):

  • rbac_group_id (nullable string/GUID)
  • rbac_role_assignment_id (nullable string/GUID)
  • rbac_role_key (nullable string; z. B. policy_profile_manager)
  • rbac_scope_mode (nullable string; z. B. global|group)
  • rbac_scope_id (nullable string/GUID)

Hinweis: Wenn ihr strikt ohne zusätzliche Felder arbeiten wollt, geht es auch rein über Discovery, aber gespeicherte IDs machen den Wizard deutlich stabiler und schneller.


Functional Requirements (additions)

  • FR-023: System MUST expose a per-tenant onboarding wizard “Setup Intune RBAC” in Filament.
  • FR-024: Wizard MUST use delegated admin login and MUST NOT store delegated tokens.
  • FR-025: Wizard MUST be idempotent (re-run safe) and MUST converge to the desired RBAC state.
  • FR-026: Wizard MUST support group-based RBAC membership (recommended) and MUST ensure the service principal is a member.
  • FR-027: Wizard MUST create or update Intune role assignments with an explicit role + scope.
  • FR-028: Wizard MUST run a post-setup verification that updates tenant health and permissions UI.
  • FR-029: Wizard MUST write audit logs for start, each step outcome, and final result (success/failed/partial).
  • FR-030: Wizard MUST enforce tenant isolation and use explicit tenant context (no implicit defaults).

Non-Functional / Safety Requirements

  • Least Privilege:
    • Default role selection is non-global admin (Policy/Profile manager).
    • Selecting higher-privilege roles shows a warning and requires explicit confirmation.
  • Clear Failure UX:
    • Every failure must map to an actionable message (e.g., “Admin consent missing”, “Insufficient directory permissions”).
  • No secrets:
    • No access tokens, secrets, or payloads in logs/audits.
  • Deterministic logging:
    • Audit entries include tenant_id, actor_user_id, action_key, resource IDs (group, roleAssignment), and status.

Acceptance Scenarios

  1. Missing RBAC → Wizard fixes it
  • Given ein aktiver Tenant mit konfigurierter App (app_client_id) und Admin Consent, aber Intune Calls liefern RBAC-403,
  • When der Admin den Wizard ausführt,
  • Then wird Gruppe+Membership+RoleAssignment hergestellt, Verify wird OK, und Policy Sync funktioniert.
  1. Admin Consent fehlt
  • Given app_client_id ist gesetzt, aber der Service Principal kann nicht aufgelöst werden,
  • When Wizard startet,
  • Then bricht er mit “Bitte zuerst Admin Consent durchführen” ab und schreibt Audit sp_not_found.
  1. Idempotenz
  • Given Wizard wurde bereits erfolgreich ausgeführt,
  • When Wizard erneut mit gleichen Einstellungen ausgeführt wird,
  • Then werden keine Duplikate erzeugt, und die Summary zeigt “No changes / Already compliant”.
  1. Insufficient privileges
  • Given ein Admin loggt sich ein, aber hat nicht die nötigen Rechte,
  • When Setup ausgeführt wird,
  • Then stoppt der Wizard mit klarer Fehlermeldung pro Step (z. B. group create / role assignment create), und Audit enthält den Step und Fehlercode.
  1. Restricted scope
  • Given Admin wählt eine eingeschränkte Scope Group,
  • When Setup abgeschlossen ist,
  • Then Verify markiert ggf. “Partial” mit Hinweis “Inventory limited by scope”.

Implementation Notes (for plan.md linkage)

  • Reuse existing services pattern:
    • IntuneRbacSetupService (new) in app/Services/Intune/
    • Uses GraphClientInterface and existing error mapping/logging hooks.
    • Uses AuditLogger for stepwise audit events.
  • Extend TenantPermissionService (User Story 7 in tasks) to include an RBAC check state:
    • status: ok|missing|error
    • message: “Intune RBAC role assignment missing (Wizard required)”
  • Add Filament wizard page/action under TenantResource.

Edge Cases

  • Group exists but is not security-enabled → fail with actionable message.
  • Role assignment exists but wrong scope → patch and warn.
  • Multiple “similar” groups by name → prefer stored rbac_group_id if present, else prompt.
  • Tenant mismatch: Wizard must never operate on non-selected tenant (enforce Tenant::current() or explicit tenant param).
  • Token expiry mid-run → show “Please retry” + audit partial.

Previous draft archived under spechistory/spec.md