507 lines
30 KiB
Markdown
507 lines
30 KiB
Markdown
---
|
||
|
||
description: "Task list for TenantPilot v1 implementation"
|
||
---
|
||
|
||
# Tasks: TenantPilot v1
|
||
|
||
**Input**: Design documents from `.specify/spec.md` and `.specify/plan.md`
|
||
**Prerequisites**: plan.md (complete), spec.md (complete)
|
||
|
||
**Status snapshot**
|
||
- Done: Phases 1–13 (US1–US4, Settings normalization/display, Highlander, permissions/health, housekeeping/UX, ops)
|
||
- Next up: Phase 14 (US8) delegated Intune RBAC onboarding wizard (synchronous)
|
||
|
||
## Phase 1: Setup (Shared Infrastructure)
|
||
|
||
- [x] T001 [P] [Shared] Confirm Sail/Env ready; ensure `.env` has PostgreSQL settings for Sail and Filament admin user seeded (if missing) in `database/seeders/`.
|
||
- [x] T002 [P] [Shared] Add baseline docs for local dev and staging promotion notes in `README.md` (Sail commands, staging-before-prod reminder).
|
||
|
||
## Phase 2: Foundational (Blocking Prerequisites)
|
||
|
||
- [x] T003 [Shared] Add tenant-aware migrations for `tenants`, `policies`, `policy_versions`, `backup_sets`, `backup_items`, `restore_runs`, `audit_logs` with JSONB payloads and FK/time indexes in `database/migrations/`.
|
||
- [x] T004 [Shared] Create models with relationships and guarded attributes for the above entities in `app/Models/`.
|
||
- [x] T005 [Shared] Implement Graph abstraction contracts (`GraphClientInterface`, error mapping, logging hooks) in `app/Services/Graph/` with a mockable adapter.
|
||
- [x] T006 [Shared] Add audit logging service/helper to capture actor, tenant, operation, resources, outcome in `app/Services/Intune/AuditLogger.php`.
|
||
- [x] T007 [Shared] Seed supported policy types/metadata for initial scope in `database/seeders/PoliciesSeeder.php` and ensure tenant scoping.
|
||
|
||
## Phase 3: User Story 1 - Policy inventory listing (Priority: P1)
|
||
|
||
### Tests for User Story 1
|
||
|
||
- [x] T008 [P] [US1] Feature test for Filament policy listing and filtering (tenant-scoped) in `tests/Feature/Filament/PolicyListingTest.php` using mocked Graph sync.
|
||
|
||
### Implementation for User Story 1
|
||
|
||
- [x] T009 [US1] Implement policy sync/import orchestrator using Graph abstraction in `app/Services/Intune/PolicySyncService.php` (no direct Graph in UI).
|
||
- [x] T010 [US1] Create Filament resource/table for policies with filters and metadata columns in `app/Filament/Resources/PolicyResource.php`.
|
||
- [x] T011 [US1] Add command/job to sync policies (queues-ready) in `app/Console/Commands/SyncPolicies.php` and queue job under `app/Jobs/`.
|
||
|
||
## Phase 4: User Story 2 - Backup creation and browsing (Priority: P1)
|
||
|
||
### Tests for User Story 2
|
||
|
||
- [x] T012 [P] [US2] Feature test for creating backup sets with multiple policies and verifying immutable JSONB snapshots + audit log in `tests/Feature/Filament/BackupCreationTest.php`.
|
||
|
||
### Implementation for User Story 2
|
||
|
||
- [x] T013 [US2] Implement backup domain service to assemble snapshots from policies with Graph payload retrieval in `app/Services/Intune/BackupService.php`.
|
||
- [x] T014 [US2] Add Filament resource/pages for backup sets and items (list/detail) in `app/Filament/Resources/BackupSetResource.php`.
|
||
- [x] T131 [UX] [US2] Refactor BackupSet policy selection to RelationManager:
|
||
- Remove the multi-select policy picker from the BackupSet **Create** form (keep Create minimal: name/description).
|
||
- After create, redirect to BackupSet **Edit/View** where items can be managed.
|
||
- Add `BackupItemsRelationManager` to `BackupSetResource` showing a table with columns: Policy Name, Type (badge), Restore (badge), Risk (badge).
|
||
- Add header action “Policies hinzufügen” (searchable, multiple) that adds items/attaches policies **tenant-scoped** and prevents duplicates per BackupSet.
|
||
- Provide a remove action (detach/soft-delete as per domain rules).
|
||
|
||
- [x] T132 [P] [US2] Update/extend `tests/Feature/Filament/BackupCreationTest.php` to cover the new UX flow:
|
||
- Create BackupSet without policies.
|
||
- Add multiple policies via RelationManager action.
|
||
- Verify immutable JSONB snapshots + audit log behavior remains correct.
|
||
- [x] T015 [US2] Wire audit logging for backup creation events in `app/Services/Intune/BackupService.php` using `AuditLogger`.
|
||
|
||
## Phase 5: User Story 3 - Version history and diff (Priority: P1)
|
||
|
||
### Tests for User Story 3
|
||
|
||
- [x] T016 [P] [US3] Feature test for version capture and timeline display in `tests/Feature/Filament/PolicyVersionTest.php`.
|
||
- [x] T017 [P] [US3] Unit test for diff generation (human summary + JSON diff) in `tests/Unit/VersionDiffTest.php`.
|
||
|
||
### Implementation for User Story 3
|
||
|
||
- [x] T018 [US3] Implement version capture service with immutable JSONB writes in `app/Services/Intune/VersionService.php`.
|
||
- [x] T019 [US3] Create diff helper (summary + structured JSON) in `app/Services/Intune/VersionDiff.php` and surface in Filament version compare view in `app/Filament/Resources/PolicyVersionResource.php`.
|
||
- [x] T020 [US3] Hook version capture into relevant flows (manual trigger + backup/restore hooks) ensuring audit logging.
|
||
|
||
## Phase 6: User Story 4 - Restore with preview and confirmation (Priority: P1)
|
||
|
||
### Tests for User Story 4
|
||
|
||
- [x] T021 [P] [US4] Feature test for restore preview (change summary, conflicts, selective items) in `tests/Feature/Filament/RestorePreviewTest.php`.
|
||
- [x] T022 [P] [US4] Feature test for confirmed restore execution capturing audit logs and per-item outcomes in `tests/Feature/Filament/RestoreExecutionTest.php`.
|
||
|
||
### Implementation for User Story 4
|
||
|
||
- [x] T023 [US4] Implement restore service with preview/dry-run and selective item application in `app/Services/Intune/RestoreService.php`, integrating Graph adapter and conflict detection.
|
||
- [x] T024 [US4] Add Filament restore UI (wizard or pages) showing preview, warnings, and confirmation gate in `app/Filament/Resources/RestoreRunResource.php`.
|
||
- [x] T025 [US4] Record restore run lifecycle (start, per-item result, completion) and audit events in `restore_runs` and `audit_logs`.
|
||
|
||
## Phase 7: User Story 5 - Operational readiness and environments (Priority: P2)
|
||
|
||
### Implementation for User Story 5
|
||
|
||
- [x] T026 [US5] Document Dokploy staging→production promotion steps, required env vars, queue/worker expectations, and migration safety notes in `README.md` or `docs/deploy.md`.
|
||
- [x] T027 [US5] Add quick Sail commands and test invocation notes to `README.md` (e.g., `./vendor/bin/sail artisan test`) and ensure sample env entries for Graph credentials.
|
||
## Phase 8: User Story 6 - Tenant hinzufügen & Entra ID App-Setup (Priority: P1)
|
||
|
||
- [x] T030 [US6] Migration für `tenants` ergänzen/prüfen:
|
||
- Felder: `name`, `tenant_id` (GUID), `domain`, `app_client_id`, `app_status`, `app_notes`,
|
||
`created_at`, `updated_at`.
|
||
- Optional: Felder für Secret/Certificate-Config (verschlüsselt), falls benötigt.
|
||
|
||
- [x] T031 [US6] Eloquent Model `Tenant`:
|
||
- Beziehungen zu `policies`, `backup_sets`, `restore_runs`, `policy_versions`, `audit_logs`
|
||
über `tenant_id`.
|
||
- Tenant-aware Scopes, falls vorhanden (z. B. `forTenant()`).
|
||
|
||
- [x] T032 [US6] Filament-Resource `TenantResource`:
|
||
- Listenansicht: Name, Tenant ID, Domain, App-Status, erstellt/am.
|
||
- Create/Edit-Form: Name, Tenant ID, Domain, App-Client-ID, optionale Notizen.
|
||
- Detailseite mit Actions:
|
||
- „Open in Entra“ (Link zur App/Tenant im Entra-Portal),
|
||
- optional: „Copy Admin Consent URL“.
|
||
|
||
- [x] T033 [US6] `TenantConfigService` (oder Erweiterung des Graph-Clients):
|
||
- Methode `testConnectivity(Tenant $tenant)`: führt einen einfachen Graph-Call aus
|
||
(z. B. `/organization` oder ähnliches) mit den App-Daten des Tenants.
|
||
- Rückgabe: DTO/Array mit `success`, `error_message` (falls vorhanden).
|
||
|
||
- [x] T034 [US6] Action „Verify configuration“ in `TenantResource`:
|
||
- Ruft `testConnectivity()` auf,
|
||
- setzt `app_status` auf z. B. `ok`, `error` oder `consent_required`,
|
||
- zeigt eine Filament-Notification mit dem Ergebnis,
|
||
- schreibt einen Audit-Log-Eintrag (`tenant.config.verified`).
|
||
|
||
- [x] T035 [US6] Tenant-Kontext in bestehende Services integrieren:
|
||
- `PolicySyncService`, `BackupService`, `RestoreService` so anpassen,
|
||
dass sie einen `Tenant` oder `tenant_id` übergeben bekommen
|
||
und den Graph-Client mit diesem Kontext verwenden.
|
||
- Sicherstellen, dass alle policy/backup/restore/audit-Datensätze `tenant_id` setzen.
|
||
|
||
- [x] T036 [US6] Feature-Test `TenantSetupTest`:
|
||
- Erstellen eines Tenants via Filament (Create-Form).
|
||
- Aufruf der Action „Verify configuration“ mit gemocktem Graph-Client:
|
||
- einmal mit erfolgreichem Call → `app_status = ok`,
|
||
- einmal mit Fehler → `app_status = error` + passende Notification.
|
||
- Prüfen, dass Audit-Logs geschrieben werden.
|
||
|
||
- [x] T037 [US6] Admin-Consent Callback Route
|
||
- Route/Controller, der als `redirect_uri` der Entra-ID-App dient.
|
||
- Liest `tenant` / `error` / `admin_consent` aus der Query.
|
||
- Ordnet das dem richtigen `Tenant` zu (z. B. via `state`).
|
||
- Aktualisiert `app_status` (z. B. `ok`, `error`, `consent_denied`).
|
||
- Zeigt eine Bestätigungs-/Fehlerseite für den Admin.
|
||
---
|
||
|
||
## Phase 9: User Story 7 - Berechtigungsübersicht & Health-Status (Priority: P1)
|
||
|
||
- [x] T040 [US7] Zentrale Permissions-Liste anlegen:
|
||
- `config/intune_permissions.php` mit allen aktuell benötigten Graph-Berechtigungen:
|
||
- technischer Name (z. B. `DeviceManagementConfiguration.ReadWrite.All`),
|
||
- Typ: `application` / `delegated`,
|
||
- kurze Beschreibung,
|
||
- Feature-Tags (z. B. `["policy-sync", "backup"]`).
|
||
- Optional: `docs/permissions.md` mit einer Tabelle Feature ↔ Permission als
|
||
menschlich lesbare Referenz.
|
||
|
||
- [x] T041 [US7] Datenmodell für Tenant-Berechtigungen:
|
||
- Variante A (einfach): JSONB-Feld `granted_permissions` in `tenants` (Liste von Permission-Keys).
|
||
- Variante B (feiner): Tabelle `tenant_permissions` mit
|
||
`(tenant_id, permission_key, status, last_checked_at)`.
|
||
- `status` mindestens: `ok`, `missing`, `error`.
|
||
|
||
- [x] T042 [US7] Service `TenantPermissionService`:
|
||
- `getRequiredPermissions(): array` – liest aus `config/intune_permissions.php`.
|
||
- `getGrantedPermissions(Tenant $tenant): array` – liest aus Graph oder aus
|
||
`tenant_permissions`/`granted_permissions`.
|
||
- `compare(Tenant $tenant): TenantPermissionStatusDTO` – liefert pro Permission
|
||
den Status (ok/missing/error) + Gesamthealth.
|
||
|
||
- [x] T043 [US7] Integration in Tenant-Detail-UI:
|
||
- Auf der `TenantResource`-Detailseite ein Panel/Section „Permissions“:
|
||
- Liste aller **required permissions**,
|
||
- pro Zeile: Name, Typ, Feature-Tags, Status (Icon + Label: OK/fehlt/Fehler).
|
||
- Optional: Link zu Doku oder Entra-Darstellung (z. B. „How to grant these permissions“).
|
||
|
||
- [x] T044 [US7] Action „Verify configuration“ erweitern:
|
||
- Zusätzlich zu `testConnectivity()` auch `TenantPermissionService::compare()` aufrufen.
|
||
- Ergebnisse in `tenant_permissions`/`granted_permissions` speichern.
|
||
- `app_status` und Permission-Health aktualisieren.
|
||
- Audit-Log-Eintrag `tenant.permissions.checked` schreiben.
|
||
|
||
- [x] T045 [US7] Tests für Permissions:
|
||
- Unit-Tests für `TenantPermissionService::compare()`:
|
||
- Szenarien: alle ok, Permission fehlt, Graph-Error.
|
||
- Feature-Test für Tenant-Detailseite:
|
||
- required permissions werden angezeigt,
|
||
- fehlende werden als fehlend markiert,
|
||
- „Verify configuration“ aktualisiert den Status wie erwartet.
|
||
|
||
## Phase 9b: Scope-Ausrichtung auf neue Objekttypen
|
||
|
||
- [x] T028 [Scope] Konfiguration `config/tenantpilot.php` auf die in `scope.supported_types` definierten Objekttypen erweitern (type/key, endpoint, label/category, optional risk/restore-Hinweis). Sicherstellen, dass diese Liste die einzige Quelle für Policy-Sync/Backup/Restore ist.
|
||
- [x] T029 [Scope] Filament-UI an neue Typen anpassen: Tabellenfilter/Grouping nach Kategorie (z. B. Config/Compliance/Scripts/Apps/CA), Backup/Restore-Formulare mit Hinweisen zu Restore-Level aus `scope.restore_matrix` (z. B. CA/enrollment restrictions = preview-only).
|
||
|
||
|
||
## Phase 10: Housekeeping – Delete-Funktionen für Backups & Versions
|
||
|
||
- [x] T060 [HK] BackupSets soft deletable machen:
|
||
- `backup_sets` (und ggf. `backup_items`) Migration/Model mit `SoftDeletes` (deleted_at).
|
||
- Sicherstellen, dass RestoreRuns keine gelöschten BackupSets verwenden; Delete nur erlauben,
|
||
wenn keine zugehörigen RestoreRuns existieren.
|
||
|
||
- [x] T061 [HK] Filament-Delete-Action für BackupSets:
|
||
- In `BackupSetResource` Delete-Action in List- und/oder Detail-View hinzufügen.
|
||
- Mit Confirmation-Dialog (“This will archive this backup set and hide it from the UI.”).
|
||
- Delete disabled/hidden, wenn `restore_runs` für das Set existieren.
|
||
- Nach Delete Audit-Log (`backup.deleted`) schreiben.
|
||
|
||
- [x] T062 [HK] PolicyVersions soft deletable machen:
|
||
- `policy_versions` Migration/Model um `SoftDeletes` erweitern.
|
||
- Alle Queries und Filament-Resources so lassen, dass standardmäßig nur non-deleted Versions
|
||
angezeigt werden.
|
||
|
||
- [x] T063 [HK] Filament-Delete-Action für PolicyVersions:
|
||
- In `PolicyVersionResource` Delete-Action hinzufügen (List/Detail).
|
||
- Confirmation + Audit-Log (`policy_version.deleted`).
|
||
|
||
- [x] T064 [HK] Tests für Housekeeping:
|
||
- Feature-Test: Löschen eines BackupSets ohne RestoreRun → `deleted_at` gesetzt, UI-Eintrag weg,
|
||
Audit-Log vorhanden.
|
||
- Feature-Test: BackupSet mit RestoreRun → Delete-Action nicht verfügbar.
|
||
- Feature-Test: Löschen einer PolicyVersion → `deleted_at` gesetzt, nicht mehr in List sichtbar.
|
||
|
||
## Phase 11: Housekeeping – Tenant löschen/deaktivieren
|
||
|
||
- [x] T070 [HK] Tenants soft deletable machen:
|
||
- `tenants` Model um `SoftDeletes` erweitern, Migration ggf. `deleted_at` hinzufügen.
|
||
- Optional: Feld `status` (enum/string: `active`, `archived`) einführen; beim Delete auf `archived` setzen.
|
||
- Alle Standard-Queries für Tenants nur `active` / nicht gelöscht anzeigen.
|
||
|
||
- [x] T071 [HK] Tenant-Delete-Action (Deaktivieren) in `TenantResource`:
|
||
- Delete-/Archive-Action in der Tenant-Liste und/oder Detailseite hinzufügen.
|
||
- Deutlich machen: “Deaktiviert diesen Tenant. Historische Daten bleiben vorhanden, neue Aktionen
|
||
sind nicht mehr möglich.”
|
||
- Bei Ausführung:
|
||
- `deleted_at` setzen (und ggf. `status = archived`),
|
||
- Audit-Log `tenant.deleted` oder `tenant.archived` schreiben.
|
||
|
||
- [x] T072 [HK] Verhalten für deaktivierte Tenants:
|
||
- In `PolicySyncService`, `BackupService`, `RestoreService` prüfen, dass nur aktive Tenants
|
||
verwendet werden; bei deaktiviertem Tenant frühzeitig mit verständlicher Fehlermeldung abbrechen.
|
||
- In Filament-Navigation Tenants, Policies, Backups, Restores eines deaktivierten Tenants nicht
|
||
mehr in Standard-Listen anzeigen (es sei denn, es gibt explizite “Show archived”-Filter).
|
||
|
||
- [x] T073 [HK] (Optional) RestoreRuns soft deletable machen:
|
||
- `restore_runs` Model/Migration mit `SoftDeletes`.
|
||
- Delete-Action in `RestoreRunResource` hinzufügen (nur UI-Aufräumung, keine Folgen für Backups).
|
||
- Audit-Log `restore_run.deleted` schreiben.
|
||
|
||
- [x] T074 [HK] Tests für Tenant-Delete:
|
||
- Feature-Test: Tenant löschen/deaktivieren → Tenant taucht nicht mehr in Standardlisten auf,
|
||
`deleted_at` (und `status`) ist gesetzt, Audit-Event existiert.
|
||
- Feature-Test: Versuch, mit deaktiviertem Tenant einen Policy-Sync/Backup/Restore zu starten,
|
||
führt zu einem klaren Fehler (und kein Graph-Call wird ausgeführt).
|
||
|
||
## Phase 12: Housekeeping – Hard Deletes (Force Delete)
|
||
|
||
- [x] T075 [HK] Force-Delete-Actions ergänzen:
|
||
- Filament-Listen für Tenants, BackupSets, PolicyVersions, RestoreRuns erhalten „Force delete“
|
||
Aktionen (sichtbar nur im Trashed-Filter), mit klarer Confirmation.
|
||
- BackupSets: Force delete nur, wenn keine RestoreRuns existieren; löscht Items mit.
|
||
- Tenants: Force delete nur, wenn archiviert; blockiert für aktive Tenants.
|
||
- Alle Force-Deletes schreiben Audit-Log-Einträge vor der endgültigen Löschung.
|
||
- Tests für Force-Delete-Flows (erfolgreich/blockiert) ergänzen.
|
||
## Phase 12: Single current tenant ("Highlander")
|
||
|
||
- [x] T120 [TENANT] Migration `add_is_current_to_tenants`:
|
||
- Spalte `is_current` (boolean, default false, not null) zu `tenants` hinzufügen.
|
||
- Partielle Unique-Index anlegen, z. B.:
|
||
- `UNIQUE INDEX tenants_current_unique ON tenants (is_current)
|
||
WHERE is_current = true AND deleted_at IS NULL`.
|
||
|
||
- [x] T121 [TENANT] Tenant-Model anpassen:
|
||
- Methode `makeCurrent()` implementieren:
|
||
- Transaktion: alle anderen Tenants `is_current = false`, dieser Tenant `is_current = true`.
|
||
- Methode `static current()` implementieren:
|
||
- Wenn `INTUNE_TENANT_ID` gesetzt ist → Tenant mit dieser GUID laden,
|
||
sonst Exception, wenn nicht gefunden / deaktiviert.
|
||
- Wenn nicht gesetzt → Tenant mit `is_current = true` und `status = active`
|
||
(und `deleted_at` null) zurückgeben.
|
||
- Wenn keiner → Exception “No current tenant selected”.
|
||
- `findOrCreateDefault()` deprecaten/entfernen; keine Dummy-Tenants mehr erzeugen.
|
||
|
||
- [x] T122 [TENANT] Data-Migration / Cleanup:
|
||
- Falls mindestens ein Tenant mit `app_status = ok` existiert:
|
||
- einen als `is_current = true` markieren (z. B. den ersten).
|
||
- `local-tenant` auf `status = archived`, `is_current = false` setzen.
|
||
- Sicherstellen, dass `local-tenant` nie wieder als aktueller Kontext verwendet wird.
|
||
|
||
- [x] T123 [TENANT] Filament `TenantResource` UI:
|
||
- Spalte/Badge für `is_current` in der Liste hinzufügen.
|
||
- Table-Action "Make current" ergänzen:
|
||
- nur sichtbar für aktive Tenants, die nicht `is_current` sind.
|
||
- ruft `makeCurrent()` auf und zeigt Notification.
|
||
- Alte Logik entfernen, die `local-tenant` automatisch als Default nutzt.
|
||
|
||
- [x] T124 [TENANT] Consumers refactoren:
|
||
- Alle Vorkommen von `findOrCreateDefault()` suchen und durch `Tenant::current()`
|
||
(oder expliziten Tenant) ersetzen:
|
||
- Policy-Sync (Command + Filament-Action),
|
||
- BackupSet-Erstellung,
|
||
- RestoreRun-Erstellung,
|
||
- ggf. weitere Services.
|
||
|
||
- [x] T125 [TENANT] Tests:
|
||
- Unit-Tests für `Tenant::current()`:
|
||
- INTUNE_TENANT_ID gesetzt → nimmt diesen Tenant, Fehler wenn nicht vorhanden.
|
||
- INTUNE_TENANT_ID nicht gesetzt → nimmt den mit `is_current = true`.
|
||
- kein current Tenant → Exception.
|
||
- Feature-Test für "Make current" in `TenantResource`:
|
||
- Nach der Action ist genau ein Tenant `is_current = true`, alle anderen `false`.
|
||
- Optional: Test, dass `local-tenant` nach Cleanup nicht mehr als Kontext gewählt wird.
|
||
|
||
- [x] T130 [UX] Tabellen-Aktionen in Dropdown bündeln (ActionGroup)
|
||
- In `TenantResource` (Tenants-Liste) die Zeilen-Aktionen refaktorieren:
|
||
- `View` (optional) direkt anzeigen.
|
||
- Alle weiteren Aktionen (`Edit`, `Admin consent`, `Verify configuration`,
|
||
`Deactivate`, `Force delete`) in eine `Tables\Actions\ActionGroup` mit
|
||
"⋯"-Icon verschieben.
|
||
- Prüfen, ob in anderen Ressourcen mit vielen Row-Actions (z.B. Backups,
|
||
RestoreRuns) ebenfalls eine `ActionGroup` sinnvoll ist und diese konsistent
|
||
einsetzen.
|
||
|
||
## Phase 13: Settings Normalization & Display (Priority: P1)
|
||
|
||
**User Story**: US1b - "Admin can open a policy detail page and see the **effective Intune settings** in a readable, normalized way (not raw JSON dumps)"
|
||
|
||
**Prerequisites**: Phase 3 (Policy inventory) complete, PolicyResource and PolicyVersionResource exist
|
||
|
||
**Scope**: FR-019 / FR-019a / FR-019b, Edge-001 (malformed snapshots), Edge-002 (@odata.type mismatch), NFR-006 (normalization coverage) per .specify/plan.md dated 2025-12-10.
|
||
|
||
### Tests for User Story 1b
|
||
|
||
- [x] T140 [P] [US1b] Unit test for PolicyNormalizer service in `tests/Unit/PolicyNormalizerTest.php`:
|
||
- Test OMA-URI/custom policy transformation → path/value table structure
|
||
- Test Settings Catalog policy transformation → flattened key-value structure
|
||
- Test standard object transformation → labeled key-value with metadata filtered
|
||
- Test edge case: malformed snapshot (keys lost) → returns warning indicator
|
||
- Test edge case: @odata.type mismatch detection → returns mismatch flag
|
||
|
||
- [x] T141 [P] [US1b] Feature test for Settings section in Policy detail view in `tests/Feature/Filament/PolicySettingsDisplayTest.php`:
|
||
- Create policy with sample JSONB snapshot (OMA-URI, Settings Catalog, standard object)
|
||
- Visit Policy detail page via PolicyResource
|
||
- Assert "Settings" section exists using Infolist component
|
||
- Assert normalized settings are displayed (not raw JSON)
|
||
- Assert metadata keys (@odata.type, internal IDs) are hidden
|
||
|
||
- [x] T142 [P] [US1b] Feature test for pretty JSON + normalized settings in Version detail in `tests/Feature/Filament/PolicyVersionSettingsTest.php`:
|
||
- Create policy version with JSONB snapshot
|
||
- Visit PolicyVersion detail page via PolicyVersionResource
|
||
- Assert pretty-printed JSON exists (monospace, copyable format)
|
||
- Assert normalized settings section exists below JSON
|
||
- Assert both views show same data in different formats
|
||
|
||
- [x] T143 [P] [Edge] Feature test for malformed snapshot warning in `tests/Feature/Filament/MalformedSnapshotWarningTest.php`:
|
||
- Create policy version with malformed snapshot (array-only, keys lost)
|
||
- Visit detail pages (Policy and PolicyVersion)
|
||
- Assert UI warning displayed: "This snapshot may be incomplete or malformed"
|
||
- Assert partial settings display attempted with warning badge
|
||
|
||
- [x] T144 [P] [Edge] Feature test for @odata.type mismatch flag in `tests/Feature/Filament/ODataTypeMismatchTest.php`:
|
||
- Create policy with @odata.type that doesn't match expected platform/type mapping
|
||
- Visit Policy detail page
|
||
- Assert warning badge/banner displayed
|
||
- Create restore run with mismatched policy → assert validation error prevents execution
|
||
|
||
### Implementation for User Story 1b
|
||
|
||
- [x] T145 [US1b] Create PolicyNormalizer service in `app/Services/Intune/PolicyNormalizer.php`:
|
||
- Method `normalize(array $snapshot, string $policyType): array`:
|
||
- Returns `['status' => 'success|warning|error', 'settings' => [...], 'warnings' => [...]]`
|
||
- Implement OMA-URI transformation:
|
||
- Extract `omaSettings` array → convert to `[['path' => '...', 'value' => '...']]` table
|
||
- Filter metadata keys (@odata.type, id, version, etc.)
|
||
- Implement Settings Catalog transformation:
|
||
- Flatten nested `settings` or `settingsDelta` structures
|
||
- Convert to labeled key-value pairs with categories
|
||
- Implement standard object transformation:
|
||
- Extract top-level properties (displayName, description, etc.)
|
||
- Group by logical sections (General, Assignment, Advanced)
|
||
- Hide Graph-specific metadata
|
||
- Add edge case detection:
|
||
- Check for malformed snapshots (empty keys, string-only serialization)
|
||
- Check for @odata.type mismatch with policy type
|
||
- Return warnings array with human-readable messages
|
||
|
||
- [x] T146 [US1b] Add Settings Infolist section to PolicyResource ViewPolicy page in `app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php`:
|
||
- Override `infolist()` method to add "Settings" section
|
||
- Use `Infolist\Components\Section` for grouping
|
||
- Call `PolicyNormalizer::normalize()` with policy's latest snapshot
|
||
- Render normalized settings using:
|
||
- `TextEntry` for simple key-value pairs
|
||
- `RepeatableEntry` for tables (OMA-URI paths)
|
||
- `KeyValueEntry` for nested structures
|
||
- Display warnings if normalization returns warning status
|
||
- Add fallback: "No settings available" if snapshot missing/empty
|
||
|
||
- [x] T147 [US1b] Enhance PolicyVersionResource ViewPolicyVersion with pretty JSON component in `app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php`:
|
||
- Add "Snapshot" section with two subsections:
|
||
1. **Raw JSON** (collapsible):
|
||
- Use `ViewEntry` with custom view blade
|
||
- Format JSON with `json_encode($snapshot, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)`
|
||
- Wrap in `<pre><code class="language-json">` for syntax highlighting
|
||
- Add "Copy" button using Filament action
|
||
2. **Normalized Settings**:
|
||
- Call `PolicyNormalizer::normalize()` with version snapshot
|
||
- Render same structure as T146 (reuse component if possible)
|
||
- Handle edge case: malformed snapshot → show warning above both views
|
||
|
||
- [x] T148 [US1b] Integrate PolicyNormalizer output into existing views:
|
||
- Update `PolicyResource` list table: add hint column showing "Settings available" badge
|
||
- Update `PolicyVersionResource` diff view: optionally show normalized diff (if feasible)
|
||
- Ensure tenant-scoped queries when fetching policies for normalization
|
||
- Add caching consideration: normalize on-demand, don't store normalized output
|
||
|
||
- [x] T149 [Edge] Add snapshot validation helper in `app/Services/Intune/SnapshotValidator.php`:
|
||
- Method `validate(array $snapshot): ValidationResult`:
|
||
- Check for empty/null snapshot → return error
|
||
- Check for array-only structure (keys lost) → return warning
|
||
- Check for string-only serialization → return error
|
||
- Check for required Graph keys (@odata.type, displayName) → return warning if missing
|
||
- Return structured result with:
|
||
- `isValid: bool`
|
||
- `warnings: array`
|
||
- `errors: array`
|
||
- Use in PolicyNormalizer before transformation
|
||
|
||
- [x] T150 [Edge] Add @odata.type validator in Policy/BackupItem models:
|
||
- Add static method `Policy::validateODataType(array $snapshot, string $expectedType): bool`
|
||
- Check if `$snapshot['@odata.type']` matches expected Graph type for policy platform/type
|
||
- Create mapping array:
|
||
```php
|
||
'deviceConfiguration' => [
|
||
'windows' => '#microsoft.graph.windowsConfiguration',
|
||
'ios' => '#microsoft.graph.iosConfiguration',
|
||
// etc.
|
||
]
|
||
```
|
||
- Return bool + optional error message
|
||
- Call in BackupService before storing backup item
|
||
- Call in RestoreService before executing restore (gate check)
|
||
|
||
- [x] T151 [Edge] Display warnings in Filament UI for malformed/mismatched data:
|
||
- Update `PolicyResource/ViewPolicy` infolist:
|
||
- Add `Placeholder` component with warning icon + message if validation fails
|
||
- Show "⚠️ This snapshot may be incomplete or malformed" banner
|
||
- Update `PolicyVersionResource/ViewPolicyVersion`:
|
||
- Add warning badge next to "Snapshot" section title if malformed
|
||
- Update `RestoreRunResource` preview/execution:
|
||
- Add validation step that checks all selected items for @odata.type mismatch
|
||
- Show modal with list of problematic items + "Cancel" / "Proceed anyway" options
|
||
- Log validation warnings to audit log
|
||
- Ensure warnings are visible but not blocking (except for restore execution gate)
|
||
|
||
### Documentation for Phase 13
|
||
|
||
- [x] T152 [US1b] Update `README.md` with Settings normalization feature:
|
||
- Add section "Policy Settings Display" explaining:
|
||
- Normalized views for better readability
|
||
- Supported policy types (OMA-URI, Settings Catalog, standard objects)
|
||
- Edge case handling (malformed snapshots, type mismatches)
|
||
- Add screenshot/example of Settings section in Policy detail
|
||
|
||
- [x] T153 [US1b] Add inline documentation in PolicyNormalizer:
|
||
- PHPDoc blocks for all public methods
|
||
- Examples of input/output structures in comments
|
||
- Edge case handling notes
|
||
|
||
**Phase 13 Completion Criteria**:
|
||
- All 14 tasks (T140-T153) completed
|
||
- Tests passing for normalization logic and UI display
|
||
- Policy detail shows readable Settings section
|
||
- Version detail shows pretty JSON + normalized settings
|
||
- Edge cases handled with clear warnings
|
||
- Constitution Check: still 7/7 ✅ (auditability maintained, safety-first for restore gates)
|
||
|
||
## Phase 14: User Story 8 – Intune RBAC Onboarding Wizard (Delegated, Synchronous)
|
||
|
||
**Scope**: FR-023 to FR-030; delegated login and grant run synchronously in Filament (no queue for grant). Optional jobs/CLI only for CHECK/REPORT (no grant).
|
||
|
||
- [ ] T160 [US8] Add TenantResource ActionGroup entry “Setup Intune RBAC”: visible for active tenants with `app_client_id`; sits alongside Admin consent/Verify; guarded by Highlander rules (no deactivated tenants).
|
||
- [ ] T161 [US8] Wizard UI (Filament): Step 1 preconditions/summary with inputs for Role (default Policy and Profile Manager, warn on Intune Admin), Scope (All devices or scope group), Group mode (create default TenantPilot-Intune-RBAC vs select existing security-enabled group); surface least-privilege warning for production.
|
||
- [ ] T162 [US8] Delegated auth step: initiate delegated login for selected tenant; stop with clear error + audit on failure/denied consent; short-lived token only (no storage).
|
||
- [ ] T163 [US8] Execution service (synchronous) with audit per step: resolve service principal by `app_client_id`; ensure/create security group; add SP as member (idempotent); ensure/create/update Intune role assignment for chosen role/scope using Graph abstraction; no queue usage.
|
||
|
||
- [ ] T164 [US8] Post-check (mandatory): force fresh token acquisition (clear tenant token cache) and run canary reads:
|
||
- `GET /deviceManagement/deviceConfigurations?$top=1`
|
||
- `GET /deviceManagement/deviceCompliancePolicies?$top=1`
|
||
- Optional: `GET /identity/conditionalAccess/policies?$top=1` only if CA features are enabled
|
||
- Update `app_status`/permissions health + write audit entries (start/login/group/member/assignment/verify outcomes).
|
||
|
||
- [ ] T165 [US8] Tests (Pest, mocked Graph): happy path; rerun idempotent (no duplicates); missing permissions → clear error mapping; scope-limited selection → warning surfaced; delegated login failure path.
|
||
|
||
- [ ] T166 [US8] Documentation: README note for wizard behavior (delegated, synchronous), least-privilege defaults, audit expectations, and how to rerun safely.
|
||
|
||
- [ ] T167 [US8-Optional] CLI/Job for CHECK/REPORT only (no grant) to inspect RBAC state; explicitly exclude grant actions from async/queue.
|
||
|
||
- [ ] T168 [US8] Extend Verify configuration / Health panel to include “Intune RBAC status”:
|
||
- Determine whether the configured Enterprise App (service principal) is covered by an Intune role assignment for required scopes (OK / Missing / Error).
|
||
- Show actionable message (“Run Setup Intune RBAC”) when missing.
|
||
- Persist last_checked_at + short reason code; log audit `tenant.rbac.checked`.
|
||
|
||
- [ ] T169 [US8] Persist RBAC artifacts on Tenant for stable idempotency:
|
||
- Add nullable columns to `tenants`: `rbac_group_id`, `rbac_role_assignment_id`, `rbac_role_key`, `rbac_scope_mode`, `rbac_scope_id`.
|
||
- Prefer stored IDs on reruns; fall back to discovery only if missing.
|
||
- Add migration + model casts; include IDs in audit context (IDs only; no secrets).
|