36 KiB
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: enabled
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:
- 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.
- Given filtering by type/category, When the admin selects a type, Then only matching objects appear and the view remains tenant-scoped.
- 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 intoDevice 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:
- 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).
- 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:
-
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.
-
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.
-
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:
- 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.
- 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.
- 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:
-
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.
-
Given selected items and explicit confirmation, When execution proceeds, Then applied changes are tenant-scoped and audit logs record start, result, and any failures.
-
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.
-
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 4b - Rerun a restore operation (Priority: P2)
Admin can rerun a previous restore operation to re-apply the same set of policies with the same settings.
Why this priority: If a restore operation fails partially, or if an admin wants to re-apply a known good configuration, a rerun option provides a quick and safe way to do so without re-selecting all policies manually.
Independent Test: From the Restore Run detail page, trigger the "Rerun" action. Verify that a new Restore Run is created with the same settings and items as the original run.
Acceptance Scenarios:
- Given a completed or failed Restore Run, When the admin triggers the "Rerun" action, Then a new Restore Run is created with the same
backup_set_id, selected items, anddry_runflag as the original run. - Given a new Restore Run created via the "Rerun" action, When the admin executes it, Then it follows the same safety gates and confirmation steps as a manually created Restore Run.
- Given a "Rerun" action is triggered, Then an audit event
restore_run.rerun_createdis logged with a reference to the originalrestore_run_id.
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:
-
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).
-
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.
-
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.
-
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:
- 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. - 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).
- 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. • FR-035: System MUST provide a "Rerun" action for any completed or failed restore run, which creates a new restore run pre-populated with the same backup set, selected items, and dry-run flag as the original run. The rerun MUST follow the same safety and confirmation gates.
(Du kannst FR-033 “live check” auch optional machen für prod, aber mindestens in CI/Staging wertvoll.)
- 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.typedoes 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_typessection 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-onlyinscope.restore_matrixno 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 withscope.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 (
deviceManagementScriptinscope.supported_types), thescriptContentMUST 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:backupdetermines full vs metadata-only snapshots;restoredetermines whether automated restore is enabled or preview-only;riskinforms warning/confirmation UX. -
FR-022: For high-risk types with
restore: preview-onlyinscope.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_currentintenants, das den aktuell verwendeten Kontext markiert. -
Die Datenbank erzwingt per partiellem Unique Index, dass höchstens ein nicht-gelöschter Tenant
is_current = truehaben kann. -
Tenant::current()liefert:- falls
INTUNE_TENANT_IDgesetzt ist, genau diesen Tenant (Fehler, wenn er nicht existiert oder deaktiviert ist), - sonst den Tenant mit
is_current = trueundstatus = active. - falls keiner gefunden wird, eine klare Exception (“No current tenant selected”); es werden keine Dummy-Tenants erzeugt.
- falls
-
In der Tenant-Verwaltung gibt es eine Action "Make current", die:
- in einer Transaktion alle anderen Tenants auf
is_current = falsesetzt und den gewählten Tenant aufis_current = true, - nur für aktive Tenants verfügbar ist.
- in einer Transaktion alle anderen Tenants auf
-
Der frühere Placeholder
local-tenantdarf nicht mehr als Graph-Kontext genutzt werden; sobald ein echter Tenant existiert, wird er archiviert und ist nieis_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)
- Service Principal (zu
- 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 consentVerify configuration
Visibility rules:
- Nur für
status=activeTenants. - Nur wenn
app_client_idgesetzt 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)
- Default:
- Scope (Dropdown):
- Default:
Global / All devices(wenn verfügbar) - Optional: Auswahl einer Scope Group / Device Group (falls euer Modell das nutzt)
- Default:
- Group Mode:
- Default:
Use Security Group (recommended) - Options:
Create new group(Default-Name:TenantPilot-Intune-RBAC)Use existing group(Picker)
- Default:
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):
- 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)
- 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.
- Validieren:
- Ergebnis-IDs werden gespeichert:
tenants.rbac_group_id(neu, optional)tenants.rbac_group_name(optional, nur für UX)
- Ensure Membership (SP ∈ Group)
- Service Principal als Member hinzufügen, wenn nicht vorhanden.
- Konflikte (already exists) müssen als OK behandelt werden.
- 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
- Post-Verify (mandatory)
- Direkt nach Setup:
Verify configurationausfü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
- 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.
- Admin Consent fehlt
- Given
app_client_idist 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.
- 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”.
- 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.
- 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) inapp/Services/Intune/- Uses
GraphClientInterfaceand existing error mapping/logging hooks. - Uses
AuditLoggerfor 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)”
- status:
- 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_idif 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