# 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 ```yaml 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)." 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." 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. --- ### 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. ### 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) 2) **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) 3) **Ensure Membership (SP ∈ Group)** - Service Principal als Member hinzufügen, wenn nicht vorhanden. - Konflikte (already exists) müssen als OK behandelt werden. 4) **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 5) **Post-Verify (mandatory)** - Direkt nach Setup: - `Verify configuration` ausführen (inkl. Permission-Matrix Update) - Zusätzlich 1–2 “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. 2) **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`. 3) **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”. 4) **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. 5) **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