merge: add spec 001 rbac onboardin
This commit is contained in:
commit
cbca4b591e
104
specs/001-rbac-onboarding/plan.md
Normal file
104
specs/001-rbac-onboarding/plan.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# Implementation Plan: [FEATURE]
|
||||||
|
|
||||||
|
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
|
||||||
|
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
|
||||||
|
|
||||||
|
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
[Extract from feature spec: primary requirement + technical approach from research]
|
||||||
|
|
||||||
|
## Technical Context
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ACTION REQUIRED: Replace the content in this section with the technical details
|
||||||
|
for the project. The structure here is presented in advisory capacity to guide
|
||||||
|
the iteration process.
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION]
|
||||||
|
**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]
|
||||||
|
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
|
||||||
|
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]
|
||||||
|
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
|
||||||
|
**Project Type**: [single/web/mobile - determines source structure]
|
||||||
|
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]
|
||||||
|
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
|
||||||
|
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]
|
||||||
|
|
||||||
|
## Constitution Check
|
||||||
|
|
||||||
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||||
|
|
||||||
|
[Gates determined based on constitution file]
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Documentation (this feature)
|
||||||
|
|
||||||
|
```text
|
||||||
|
specs/[###-feature]/
|
||||||
|
├── plan.md # This file (/speckit.plan command output)
|
||||||
|
├── research.md # Phase 0 output (/speckit.plan command)
|
||||||
|
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||||
|
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||||
|
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||||
|
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code (repository root)
|
||||||
|
<!--
|
||||||
|
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
|
||||||
|
for this feature. Delete unused options and expand the chosen structure with
|
||||||
|
real paths (e.g., apps/admin, packages/something). The delivered plan must
|
||||||
|
not include Option labels.
|
||||||
|
-->
|
||||||
|
|
||||||
|
```text
|
||||||
|
# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT)
|
||||||
|
src/
|
||||||
|
├── models/
|
||||||
|
├── services/
|
||||||
|
├── cli/
|
||||||
|
└── lib/
|
||||||
|
|
||||||
|
tests/
|
||||||
|
├── contract/
|
||||||
|
├── integration/
|
||||||
|
└── unit/
|
||||||
|
|
||||||
|
# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected)
|
||||||
|
backend/
|
||||||
|
├── src/
|
||||||
|
│ ├── models/
|
||||||
|
│ ├── services/
|
||||||
|
│ └── api/
|
||||||
|
└── tests/
|
||||||
|
|
||||||
|
frontend/
|
||||||
|
├── src/
|
||||||
|
│ ├── components/
|
||||||
|
│ ├── pages/
|
||||||
|
│ └── services/
|
||||||
|
└── tests/
|
||||||
|
|
||||||
|
# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected)
|
||||||
|
api/
|
||||||
|
└── [same as backend above]
|
||||||
|
|
||||||
|
ios/ or android/
|
||||||
|
└── [platform-specific structure: feature modules, UI flows, platform tests]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure Decision**: [Document the selected structure and reference the real
|
||||||
|
directories captured above]
|
||||||
|
|
||||||
|
## Complexity Tracking
|
||||||
|
|
||||||
|
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||||
|
|
||||||
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||||
|
|-----------|------------|-------------------------------------|
|
||||||
|
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
||||||
|
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|
||||||
709
specs/001-rbac-onboarding/spec.md
Normal file
709
specs/001-rbac-onboarding/spec.md
Normal file
@ -0,0 +1,709 @@
|
|||||||
|
# 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)."
|
||||||
|
|
||||||
|
- 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**:
|
||||||
|
|
||||||
|
1. **Given** an authenticated admin, **When** they open the Policies list, **Then** they see supported object types with identifiers, type/category, platform, and last-updated metadata.
|
||||||
|
2. **Given** filtering by type/category, **When** the admin selects a type, **Then** only matching objects appear and the view remains tenant-scoped.
|
||||||
|
3. **Given** Settings Catalog Policies exist in Intune, **When** the admin opens the Policies list and syncs, **Then** Settings Catalog Policies are listed as type `Settings Catalog Policy` (settingsCatalogPolicy) and are not mixed into `Device Configuration`.
|
||||||
|
---
|
||||||
|
### User Story 1b - Policy detail shows readable settings (Priority: P1)
|
||||||
|
|
||||||
|
Admin can open a policy detail page and see the **effective Intune settings** in a readable, normalized way (not raw JSON dumps).
|
||||||
|
|
||||||
|
**Independent Test**: From Filament, open a policy detail view; verify a "Settings" section renders normalized key/value pairs (or tables for special cases) derived from the latest snapshot.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a policy with at least one captured snapshot, **When** the admin opens the policy detail view, **Then** they see a "Settings" section rendering the policy configuration in a readable format (grouped/labeled).
|
||||||
|
2. **Given** the snapshot contains nested structures or list-based settings (e.g., OMA-URI / Settings Catalog), **When** the admin views settings, **Then** values are flattened/grouped or rendered as tables, and irrelevant metadata keys are hidden.
|
||||||
|
---
|
||||||
|
### User Story 2 - Backup creation and browsing (Priority: P1)
|
||||||
|
|
||||||
|
Admin creates backup sets containing multiple objects (config, compliance, scripts, apps, CA, etc.) with immutable snapshots and can browse backup details in Filament.
|
||||||
|
|
||||||
|
**Why this priority**: Backups provide safety and enable restore; immutability and audit are foundational.
|
||||||
|
|
||||||
|
**Independent Test**: Initiate a backup set selecting multiple objects; confirm immutable JSONB snapshots persisted, audit log written, and Filament shows backup detail and items.
|
||||||
|
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** selected objects from different categories, **When** the admin creates a backup set, **Then** backup items store immutable payload snapshots (full or metadata-only as per the restore matrix) with identifiers and types.
|
||||||
|
2. **Given** a completed backup set, **When** the admin opens its detail page, **Then** all items and metadata display along with the audit record of creation.
|
||||||
|
|
||||||
|
3. **Given** mehrere Backup-Sets existieren,
|
||||||
|
**When** der Admin ein Backup-Set auswählen oder ansehen möchte,
|
||||||
|
**Then** sieht er für jedes Set:
|
||||||
|
- einen sprechenden Namen (nicht nur Timestamp),
|
||||||
|
- das Erstellungsdatum,
|
||||||
|
- die Anzahl der enthaltenen Items,
|
||||||
|
- und optional eine kurze Beschreibung, damit er das Set sinnvoll unterscheiden kann.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 3 - Version history and diff (Priority: P1)
|
||||||
|
|
||||||
|
Admin can capture versions for any supported object, view timelines, and compare any two versions with meaningful diffs.
|
||||||
|
|
||||||
|
**Why this priority**: Version visibility and diffs enable rollback readiness and change comprehension.
|
||||||
|
|
||||||
|
**Independent Test**: Create multiple versions for a given object; verify timeline ordering, version metadata, and diff output (human summary + JSON diff where feasible) between any two versions.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** an admin triggers version capture for an object, **When** the version is saved, **Then** an immutable snapshot and metadata (actor, time, type, tenant) are recorded.
|
||||||
|
2. **Given** two versions of the same object, **When** the admin requests a comparison, **Then** the UI shows a human-readable summary and structured JSON diff where available.
|
||||||
|
3. **Given** a saved policy version, **When** the admin opens the version detail page, **Then** the snapshot is displayed as pretty-printed JSON and, where possible, as normalized settings (not as an unreadable serialized array/string).
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 4 - Restore with preview and confirmation (Priority: P1)
|
||||||
|
|
||||||
|
Admin can run a restore from a backup set with preview/dry-run, selective restore, clear warnings, and required confirmation before execution.
|
||||||
|
|
||||||
|
**Why this priority**: Restore is high-risk; safety features are mandatory for production readiness.
|
||||||
|
|
||||||
|
**Independent Test**: Start a restore from a backup set in preview; view change summary and warnings; select items; confirm execution; verify audit logs and outcomes recorded (success/failure/partial).
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a backup set, **When** the admin initiates a restore in preview mode, **Then** the system shows a change summary with selectable items and conflict warnings.
|
||||||
|
2. **Given** selected items and explicit confirmation, **When** execution proceeds, **Then** applied changes are tenant-scoped and audit logs record start, result, and any failures.
|
||||||
|
|
||||||
|
3. **Given** mehrere Backup-Sets existieren,
|
||||||
|
**When** der Admin einen Restore Run erstellt,
|
||||||
|
**Then** zeigt die Auswahl für das "Backup set" mindestens:
|
||||||
|
- den Backup-Namen,
|
||||||
|
- das Erstellungsdatum,
|
||||||
|
- die Anzahl der Items,
|
||||||
|
damit der Admin das richtige Backup-Set sicher auswählen kann.
|
||||||
|
|
||||||
|
4. **Given** ein Restore Run wurde erstellt,
|
||||||
|
**When** der Admin die Detailseite des Restore Runs öffnet,
|
||||||
|
**Then** sieht er, welche Policies/Items in diesem Run enthalten sind
|
||||||
|
(z. B. Liste der Policies mit Name/Typ/Plattform).
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 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**:
|
||||||
|
|
||||||
|
1. **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, and `dry_run` flag as the original run.
|
||||||
|
2. **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.
|
||||||
|
3. **Given** a "Rerun" action is triggered, **Then** an audit event `restore_run.rerun_created` is logged with a reference to the original `restore_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**:
|
||||||
|
|
||||||
|
1. **Given** ein Tenant ist in TenantPilot hinterlegt,
|
||||||
|
**When** der Admin die Tenant-Detailseite öffnet,
|
||||||
|
**Then** sieht er eine Liste aller *erforderlichen* Berechtigungen mit Status
|
||||||
|
(z. B. OK, fehlt).
|
||||||
|
|
||||||
|
2. **Given** neue Funktionen wurden eingeführt, die zusätzliche Berechtigungen benötigen
|
||||||
|
und diese wurden in der zentralen Permissions-Liste hinzugefügt,
|
||||||
|
**When** der Admin die Tenant-Detailseite öffnet,
|
||||||
|
**Then** erscheinen die neuen Berechtigungen automatisch in der Übersicht und
|
||||||
|
fehlende Berechtigungen werden klar als fehlend markiert.
|
||||||
|
|
||||||
|
3. **Given** der Admin klickt auf "Verify configuration",
|
||||||
|
**When** TenantPilot einen Graph-Twestcall und/oder das Permission-Setup prüft,
|
||||||
|
**Then** wird der Status der Berechtigungen aktualisiert (OK/fehlt/Fehler) und
|
||||||
|
es wird ein Audit-Eintrag erstellt.
|
||||||
|
|
||||||
|
4. **Given** ein Tenant hat fehlende kritische Berechtigungen,
|
||||||
|
**When** andere Features (Policy-Sync, Backup, Restore) diesen Tenant verwenden,
|
||||||
|
**Then** kann TenantPilot dem Admin entsprechende Warnungen anzeigen oder die
|
||||||
|
Funktion mit einem klaren Fehler abbrechen.
|
||||||
|
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a fresh checkout, **When** Sail commands run (`./vendor/bin/sail up -d`, `./vendor/bin/sail artisan migrate`), **Then** the app boots with PostgreSQL and Filament admin available.
|
||||||
|
2. **Given** a pending release, **When** migrations and restore flows are validated on staging, **Then** production deployment proceeds with documented steps and environment parity.
|
||||||
|
|
||||||
|
### User Story 8 – Graph Contract Registry & Drift Guard (Priority: P1)
|
||||||
|
Admin soll sich darauf verlassen können, dass Backup/Restore/Preview nicht wegen Graph-Shape-Details (derived @odata.type, verbotene $expand/$select, Property-Abweichungen) “random” bricht.
|
||||||
|
|
||||||
|
Acceptance Scenarios:
|
||||||
|
1. Given ein Backup enthält @odata.type = #microsoft.graph.windows10CompliancePolicy,
|
||||||
|
When Preview/Restore läuft,
|
||||||
|
Then wird das als gültiger deviceCompliancePolicy-Family Typ behandelt (kein odata_mismatch), und der korrekte Endpoint/Method wird genutzt.
|
||||||
|
2. Given ein Endpoint erlaubt bestimmte Expands/Selects nicht,
|
||||||
|
When TenantPilot Requests baut,
|
||||||
|
Then werden nur “allowed capabilities” verwendet (kein 400 durch OData parsing).
|
||||||
|
3. Given Microsoft/Intune ändert Shape/Capabilities,
|
||||||
|
When graph:contract:check läuft,
|
||||||
|
Then schlägt der Check kontrolliert fehl und zeigt welcher Contract angepasst werden muss (statt dass Prod-Flows brechen).
|
||||||
|
|
||||||
|
2) 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.)
|
||||||
|
|
||||||
|
3) Implementation Notes / Data Artefacts ergänzen
|
||||||
|
|
||||||
|
Ein kleines, versioniertes Artefakt einführen, z. B.:
|
||||||
|
• config/graph_contracts.php oder .specify/contracts/graph.yaml
|
||||||
|
|
||||||
|
Darin pro Objekt-Typ:
|
||||||
|
• resource (collection + single-item path)
|
||||||
|
• type_family (Liste erlaubter @odata.type)
|
||||||
|
• allowed_select / allowed_expand
|
||||||
|
• member_hydration_strategy (z. B. “property array” vs “subresource” vs “not available”)
|
||||||
|
• create_method / update_method / id_field
|
||||||
|
|
||||||
|
Das verhindert “Wissens-Leaks” quer durch Services.
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
- Graph permissions missing or expired, causing policy fetch/restore failures with clear error mapping and audit entries.
|
||||||
|
- Large policy payloads or many items in a backup set; ensure JSONB storage and pagination handle load without timeouts.
|
||||||
|
- Restore conflicts when target tenant already has newer versions; preview must surface warnings and allow skip.
|
||||||
|
- Partial restore failures; audits must capture per-item outcomes and surface retry guidance.
|
||||||
|
- Diff generation for incompatible or malformed payloads should fail gracefully with admin-facing messaging.
|
||||||
|
- Retention/size concerns for snapshots; document defaults and guard against unbounded growth.
|
||||||
|
- Snapshots stored as serialized strings or array-only dumps (keys lost) must be detected; UI should show a clear warning and fall back to raw display.
|
||||||
|
- Policies whose `@odata.type` does not match the expected platform/type mapping should be flagged to prevent wrong restore previews (e.g., stored as Windows but snapshot indicates Android).
|
||||||
|
## Requirements *(mandatory)*
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **FR-001**: System MUST list all Intune objects defined in the `scope.supported_types` section with normalized metadata and tenant scoping for selection.
|
||||||
|
- **FR-002**: System MUST allow admins to create backup sets containing multiple objects (configuration, compliance, scripts, apps, conditional access, etc.) with immutable JSONB payload snapshots.
|
||||||
|
- **FR-003**: Backup creation MUST log audit events including actor, timestamp, tenant, items, and outcome.
|
||||||
|
- **FR-004**: System MUST capture policy versions on demand and present per-policy timelines.
|
||||||
|
- **FR-005**: Users MUST be able to diff any two versions with a human-readable summary and structured JSON diff where feasible.
|
||||||
|
- **FR-006**: Restore MUST support preview/dry-run, selective item restore, and explicit confirmation before applying changes, within the per-type restore level defined in `scope.restore_matrix`.
|
||||||
|
- **FR-007**: Restore execution MUST produce audit logs covering success, failure, and partial outcomes.
|
||||||
|
- **FR-008**: Graph integration MUST route through a dedicated abstraction layer with standardized error mapping, safe retries, and high-level logging without secrets.
|
||||||
|
- **FR-009**: All policy, version, backup, and restore data MUST be tenant-aware; queries enforce tenant isolation.
|
||||||
|
- **FR-010**: Application MUST run locally via Laravel Sail with PostgreSQL and provide Filament admin flows.
|
||||||
|
- **FR-011**: Deployments MUST target Dokploy staging before production with documented migration and worker implications.
|
||||||
|
- **FR-012**: Tests MUST cover backup composition rules, version immutability, audit events, and Filament backup/restore flows (with Graph boundaries mocked).
|
||||||
|
- **FR-013**: Raw policy snapshots and backup payloads MUST be stored as JSONB with indexes justified by query needs (e.g., FK and time-based; GIN when filters require).
|
||||||
|
- **FR-014**: UI MUST provide clear warnings for potential restore conflicts and require confirmation for destructive operations; for types with `restore: preview-only` in `scope.restore_matrix` no direct apply action MAY be offered.
|
||||||
|
- **FR-015**: Admins MUST be able to safely delete (archive) backup sets that are no
|
||||||
|
longer needed. Deletion is implemented as soft-delete with audit logging, and
|
||||||
|
backup sets referenced by completed restore runs cannot be removed.
|
||||||
|
|
||||||
|
- **FR-016**: Admins MUST be able to delete individual policy versions for housekeeping.
|
||||||
|
Deletion is implemented as soft-delete with audit logging.
|
||||||
|
|
||||||
|
- **FR-017**: Admins MUST be able to deactivate (soft-delete) a tenant.
|
||||||
|
Deactivated
|
||||||
|
tenants:
|
||||||
|
- do not appear in default lists,
|
||||||
|
- cannot be used for new sync/backup/restore operations,
|
||||||
|
- keep their historical data and audit logs for traceability.
|
||||||
|
- **FR-018**: Admins MAY soft-delete restore runs to keep the UI clean; underlying
|
||||||
|
backup and policy data remains untouched.
|
||||||
|
|
||||||
|
- **FR-019**: The system MUST normalize different payload structures for display via a `PolicyNormalizer` (or equivalent): OMA-URI/custom policies as path/value tables, Settings Catalog policies as flattened structures, and standard objects as key-value views, aligned with `scope.supported_types`.
|
||||||
|
- **FR-019a**: Policy detail views MUST display a "Settings" section derived from the latest available snapshot (using the normalizer output when available).
|
||||||
|
- **FR-019b**: Policy version detail views MUST render snapshots as pretty-printed JSON (monospace, copyable) and SHOULD also render normalized settings via the same normalizer.
|
||||||
|
- **FR-020**: For PowerShell script objects (`deviceManagementScript` in `scope.supported_types`), the `scriptContent` MUST be base64-decoded when stored in backups/versions for readability/diffing and encoded again when sent back to Graph during restore.
|
||||||
|
- **FR-021**: Restore behavior MUST follow the per-type configuration in `scope.restore_matrix`: `backup` determines full vs metadata-only snapshots; `restore` determines whether automated restore is enabled or preview-only; `risk` informs warning/confirmation UX.
|
||||||
|
- **FR-022**: For high-risk types with `restore: preview-only` in `scope.restore_matrix` (e.g., `conditionalAccessPolicy`, `enrollmentRestriction`), TenantPilot MUST provide full backups, version history, and diffs plus detailed restore previews, but MUST NOT expose direct Graph apply actions; restore is manual, guided by the preview.
|
||||||
|
|
||||||
|
### Key Entities *(include if feature involves data)*
|
||||||
|
|
||||||
|
- **tenants**: Represents the deployment tenant context; referenced by all scoped data.
|
||||||
|
- **policies**: Normalized metadata for supported Intune policies.
|
||||||
|
- **policy_versions**: Immutable snapshots with metadata (actor, timestamp, tenant, policy type).
|
||||||
|
- **backup_sets**: Group of backup items with creator, timestamp, and tenant context.
|
||||||
|
- **backup_items**: Individual policy snapshots within a backup set (immutable JSONB payload + identifiers).
|
||||||
|
- **restore_runs**: Execution records for restores, including preview/actual flags and outcomes.
|
||||||
|
- **audit_logs**: Audit trail entries for backups, restores, version captures, and significant Graph actions.
|
||||||
|
|
||||||
|
## Success Criteria *(mandatory)*
|
||||||
|
|
||||||
|
### Measurable Outcomes
|
||||||
|
|
||||||
|
- **SC-001**: Admin can create a backup set selecting multiple policies and view immutable backup items with audit logs in Filament.
|
||||||
|
- **SC-002**: Policy version history timeline is available per policy and supports comparing any two versions with summary and JSON diff outputs.
|
||||||
|
- **SC-003**: Restore preview shows change summaries and conflict warnings; execution requires explicit confirmation and produces audit logs for all outcomes.
|
||||||
|
- **SC-004**: Core flows run locally via Sail; staging validation of migrations and restore paths completes before production deployments.
|
||||||
|
- **SC-005**: Automated tests covering backup composition, version immutability, audit logging, and Filament backup/restore flows pass via `./vendor/bin/sail artisan test`.
|
||||||
|
|
||||||
|
|
||||||
|
### Technical Story – Enforce Single Current Tenant ("Highlander Principle")
|
||||||
|
|
||||||
|
**Context**
|
||||||
|
|
||||||
|
Aktuell können mehrere Tenants `status = active` sein. Graph-Operationen (Policy Sync,
|
||||||
|
Backup, Restore) wählen den Kontext über Heuristiken (`findOrCreateDefault`,
|
||||||
|
`local-tenant`), was zu falschen Tenants und Fehlern führt.
|
||||||
|
|
||||||
|
**Goal**
|
||||||
|
|
||||||
|
Es soll **immer genau einen klar definierten "current" Tenant** geben, über den
|
||||||
|
alle Graph-Operationen laufen. Die Auswahl dieses Tenants ist explizit und
|
||||||
|
transparent (UI + Env), nicht implizit.
|
||||||
|
|
||||||
|
**Requirements**
|
||||||
|
|
||||||
|
- Es gibt ein Flag `is_current` in `tenants`, das den aktuell verwendeten Kontext
|
||||||
|
markiert.
|
||||||
|
- Die Datenbank erzwingt per partiellem Unique Index, dass höchstens ein
|
||||||
|
nicht-gelöschter Tenant `is_current = true` haben kann.
|
||||||
|
- `Tenant::current()` liefert:
|
||||||
|
- falls `INTUNE_TENANT_ID` gesetzt ist, **genau diesen** Tenant (Fehler, wenn
|
||||||
|
er nicht existiert oder deaktiviert ist),
|
||||||
|
- sonst den Tenant mit `is_current = true` und `status = active`.
|
||||||
|
- falls keiner gefunden wird, eine klare Exception (“No current tenant selected”);
|
||||||
|
es werden keine Dummy-Tenants erzeugt.
|
||||||
|
- In der Tenant-Verwaltung gibt es eine Action "Make current", die:
|
||||||
|
- in einer Transaktion alle anderen Tenants auf `is_current = false` setzt
|
||||||
|
und den gewählten Tenant auf `is_current = true`,
|
||||||
|
- nur für aktive Tenants verfügbar ist.
|
||||||
|
- Der frühere Placeholder `local-tenant` darf nicht mehr als Graph-Kontext genutzt
|
||||||
|
werden; sobald ein echter Tenant existiert, wird er archiviert und ist nie
|
||||||
|
`is_current`.
|
||||||
|
- Alle Graph-basierten Funktionen (Policy Sync, Backup, Restore) verwenden
|
||||||
|
konsistent `Tenant::current()` oder einen explizit übergebenen Tenant.
|
||||||
|
|
||||||
|
Tenant-level actions such as "Admin consent" and "Verify configuration"
|
||||||
|
MUST be exposed on the tenant detail view (and/or row actions), not as a
|
||||||
|
global button without explicit tenant context.
|
||||||
|
|
||||||
|
|
||||||
|
### UX Guideline – Table Actions / Dropdowns
|
||||||
|
|
||||||
|
- Tabellen in Filament mit mehr als zwei Zeilen-Aktionen (z.B. View, Edit,
|
||||||
|
Admin consent, Verify, Deactivate, Force delete) MÜSSEN ihre Aktionen in
|
||||||
|
einem kompakten Dropdown / ActionGroup bündeln, statt alle Buttons nebeneinander
|
||||||
|
anzuzeigen.
|
||||||
|
- Ausnahmen: besonders häufige, nicht-destruktive Aktionen (z.B. "View")
|
||||||
|
dürfen weiterhin als einzelner Button sichtbar bleiben; alle weiteren
|
||||||
|
Aktionen (z.B. Admin-Aktionen, Housekeeping) sollen im Dropdown liegen.
|
||||||
|
- Ziel: die Tabellen bleiben übersichtlich, Spaltenbreite wird begrenzt,
|
||||||
|
und Admins bekommen eine konsistente "⋯"-Interaktion für erweiterte Aktionen.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## User Story 7 – Intune RBAC Onboarding Wizard (Delegated Admin Login) *(Priority: P1)*
|
||||||
|
|
||||||
|
### Problem / Context
|
||||||
|
|
||||||
|
TenantPilot arbeitet primär **app-only (Client Credentials)** gegen Microsoft Graph.
|
||||||
|
Für viele Intune-Objekte reicht „Graph App Permissions + Admin Consent“ allein nicht aus: Intune kann zusätzlich über **Intune RBAC**
|
||||||
|
blockieren, wenn der **Service Principal** (Enterprise App) keine passende **Intune Role Assignment** inkl. Scope hat.
|
||||||
|
Das äußert sich typischerweise als **403** mit „Application is not authorized to perform this operation“.
|
||||||
|
|
||||||
|
Dieses Setup ist ein **Bootstrap-Problem**:
|
||||||
|
- Ohne RBAC-Zuweisung sind Intune Reads/Writes blockiert.
|
||||||
|
- Ohne ausreichende Rechte kann TenantPilot die RBAC-Zuweisung nicht „self-service“ per app-only herstellen.
|
||||||
|
|
||||||
|
**Ziel:** TenantPilot bietet pro Tenant einen **Onboarding-Wizard**, bei dem ein Admin sich **interaktiv (delegated)** anmeldet,
|
||||||
|
und TenantPilot automatisiert (idempotent) die erforderliche Intune-RBAC-Konfiguration für die konfigurierte Enterprise App herstellt.
|
||||||
|
Danach funktionieren Policy Sync / Backup / Restore (gemäß Restore Matrix) zuverlässig.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Value
|
||||||
|
|
||||||
|
- Admins können RBAC-Probleme direkt in TenantPilot beheben (kein “Portal-Rätselraten”).
|
||||||
|
- Klarer, auditierter Ablauf (wer hat wann welche Rechte/Sopes gesetzt).
|
||||||
|
- Minimiert Ausfälle bei Policy-Sync/Backup/Restore und reduziert Support-Aufwand.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### In Scope (v1)
|
||||||
|
|
||||||
|
- Wizard in Filament auf der **Tenant-Detailseite** (tenant-scoped).
|
||||||
|
- Delegated Admin Login (interaktiv).
|
||||||
|
- Idempotente Ausführung:
|
||||||
|
- Service Principal (zu `tenant.app_client_id`) auflösen
|
||||||
|
- RBAC-Membership via **Security Group (recommended)** herstellen
|
||||||
|
- Intune Role Assignment erstellen/aktualisieren (Rolle + Scope)
|
||||||
|
- Abschließender Verify-Run (Health/Permissions aktualisieren)
|
||||||
|
- Vollständige Audit-Logs pro Step.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Out of Scope (v1)
|
||||||
|
|
||||||
|
- Vollautomatisches “Self-heal” ohne Admin-Interaktion.
|
||||||
|
- Zeitgesteuerte Jobs, die RBAC-Rechte vergeben (ohne explizite Admin-Aktion).
|
||||||
|
- Unterstützung mehrerer paralleler RBAC-Profile pro Tenant (nur ein “recommended setup”).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UX / Entry Points
|
||||||
|
|
||||||
|
### Entry Point: Tenant Detail (Filament)
|
||||||
|
|
||||||
|
Auf der `TenantResource` Detailseite im Action-Dropdown:
|
||||||
|
|
||||||
|
- `Setup Intune RBAC` (Wizard)
|
||||||
|
- `Admin consent`
|
||||||
|
- `Verify configuration`
|
||||||
|
|
||||||
|
**Visibility rules:**
|
||||||
|
- Nur für `status=active` Tenants.
|
||||||
|
- Nur wenn `app_client_id` gesetzt ist.
|
||||||
|
- Optional: Badge/Hint “RBAC missing” aus Health-Check.
|
||||||
|
|
||||||
|
**Copy/Help:**
|
||||||
|
- Kurze Erklärung: “Graph Permissions ≠ Intune RBAC”.
|
||||||
|
- Hinweis auf Least Privilege.
|
||||||
|
- Klarer Hinweis, dass Änderungen tenantweit wirken (je nach Scope).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wizard Flow
|
||||||
|
|
||||||
|
### Step 1 — Configuration (Role / Scope / Group)
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- **Role** (Dropdown):
|
||||||
|
- Default: `Policy and Profile Manager` (Least Privilege für Policy/Config-Workflows)
|
||||||
|
- Optional: `Intune Administrator` (mit Warnung)
|
||||||
|
- **Scope** (Dropdown):
|
||||||
|
- Default: `Global / All devices` (wenn verfügbar)
|
||||||
|
- Optional: Auswahl einer Scope Group / Device Group (falls euer Modell das nutzt)
|
||||||
|
- **Group Mode**:
|
||||||
|
- Default: `Use Security Group (recommended)`
|
||||||
|
- Options:
|
||||||
|
- `Create new group` (Default-Name: `TenantPilot-Intune-RBAC`)
|
||||||
|
- `Use existing group` (Picker)
|
||||||
|
|
||||||
|
**UI Requirements:**
|
||||||
|
- “Review screen” zeigt *genau*, was erstellt/geändert wird (Role, Scope, Group).
|
||||||
|
|
||||||
|
### Step 2 — Delegated Admin Login
|
||||||
|
|
||||||
|
- Admin führt interaktiven Login durch (delegated).
|
||||||
|
- Wizard zeigt klar:
|
||||||
|
- welcher Tenant
|
||||||
|
- welche App (Client ID / Display Name, sofern auflösbar)
|
||||||
|
- dass nur kurzzeitig ein User-Token genutzt wird
|
||||||
|
|
||||||
|
**Security rule (mandatory):**
|
||||||
|
- Delegated Access Tokens werden **nicht persistiert** (keine Speicherung in DB/Cache).
|
||||||
|
- Tokens existieren nur im Request-Kontext / Session und werden nach Abschluss verworfen.
|
||||||
|
|
||||||
|
### Step 3 — Execute Setup (Idempotent + Safe)
|
||||||
|
|
||||||
|
Wizard führt folgenden Ablauf aus (alle Operationen tenant-scoped, über Graph-Abstraktion, mit Error-Mapping):
|
||||||
|
|
||||||
|
1) **Resolve Service Principal**
|
||||||
|
- Auflösen des Service Principals zur `tenant.app_client_id`.
|
||||||
|
- Wenn nicht gefunden:
|
||||||
|
- Wizard stoppt mit Hinweis: “Enterprise App ist im Tenant nicht vorhanden. Bitte zuerst Admin Consent durchführen.”
|
||||||
|
- Audit log: `tenant.rbac.setup.failed` (reason: sp_not_found)
|
||||||
|
|
||||||
|
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
|
||||||
937
specs/001-rbac-onboarding/tasks.md
Normal file
937
specs/001-rbac-onboarding/tasks.md
Normal file
@ -0,0 +1,937 @@
|
|||||||
|
---
|
||||||
|
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)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Constitution Evidence Ledger (Discovery + Verification)
|
||||||
|
|
||||||
|
> This section is the canonical evidence record to satisfy Constitution VII (Spec-Driven Development) and IV (Auditability).
|
||||||
|
> Each completed phase has: (a) discovery notes, (b) verification commands, (c) where to look in repo/UX.
|
||||||
|
|
||||||
|
### Evidence: Phases 1–6 (US1–US4 core)
|
||||||
|
- **Discovery:** Verified existing Filament resources and services implement tenant scoping and Graph abstraction; restore flow includes preview + confirmation; versions stored immutable JSONB; audits written for critical operations.
|
||||||
|
- **Verification:**
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/PolicyListingTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/BackupCreationTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/RestorePreviewTest.php tests/Feature/Filament/RestoreExecutionTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/PolicyVersionTest.php tests/Unit/VersionDiffTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
- **Manual checks:** Filament UI: Policies list/filter, BackupSet detail + items, RestoreRun preview/execution, PolicyVersion view/diff.
|
||||||
|
|
||||||
|
### Evidence: Phase 13 (US1b settings display + safety gates)
|
||||||
|
- **Discovery:** Normalized settings display added; malformed snapshot warnings; @odata.type mismatch gates block restore execution.
|
||||||
|
- **Verification:**
|
||||||
|
- `./vendor/bin/pest tests/Unit/PolicyNormalizerTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/PolicySettingsDisplayTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/PolicyVersionSettingsTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
|
||||||
|
### Evidence: Phase 14 (US7 RBAC wizard)
|
||||||
|
- **Discovery:** RBAC wizard stack present (TenantResource action, delegated auth controller, onboarding service, health panel, migrations, tests).
|
||||||
|
- **Verification:**
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Unit/RbacOnboardingServiceTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
|
||||||
|
### Evidence: Phase 15 (US8 Graph Contract Registry & Drift Guard)
|
||||||
|
- **Discovery:** Contract registry + fallback integrated in Graph client; drift-check command added; type-family tolerant @odata validation added.
|
||||||
|
- **Verification:**
|
||||||
|
- `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php tests/Unit/GraphContractFallbackTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/ODataTypeMismatchTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
|
||||||
|
### Evidence: Settings Catalog (settingsCatalogPolicy) extensions
|
||||||
|
- **Discovery:** Added first-class sync/type + restore hardening + hydration + normalized display improvements.
|
||||||
|
- **Verification:**
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicySyncTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogRestoreTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicyHydrationTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FR → Tasks Traceability Matrix (Explicit)
|
||||||
|
|
||||||
|
> This matrix makes FR coverage explicit (tooling/audits). Tasks also carry local Implements: tags where most useful.
|
||||||
|
|
||||||
|
- **FR-001 (Inventory)** → T008–T011
|
||||||
|
- **FR-002 (Backups)** → T012–T015, T131–T132
|
||||||
|
- **FR-003 (Auditability baseline)** → T006, T015, T020, T025
|
||||||
|
- **FR-004 (Versions)** → T016–T020
|
||||||
|
- **FR-005 (Diffs)** → T017, T019
|
||||||
|
- **FR-006–FR-010 (Restore safety + preview + gating)** → T021–T026, T144, T151
|
||||||
|
- **FR-011–FR-018 (Tenant-aware + Graph abstraction + governance basics)** → T003–T007, T035, T120–T125
|
||||||
|
- **FR-019.1–FR-019.2 (Settings normalization + edge cases)** → T140–T153
|
||||||
|
- **FR-023–FR-030 (RBAC onboarding wizard)** → T160–T169
|
||||||
|
- **FR-031–FR-034 (Contract registry + drift guard)** → T170–T175
|
||||||
|
- **FR-035 (Rerun restore)** → T156
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tasks: TenantPilot v1
|
||||||
|
|
||||||
|
|
||||||
|
## Measurable Thresholds (NFR/UX)
|
||||||
|
|
||||||
|
These thresholds make qualitative terms measurable and testable.
|
||||||
|
|
||||||
|
### Payload / Rendering Limits
|
||||||
|
- **Settings table max rows:** 1000 rows per rendered table block (truncate with notice).
|
||||||
|
- **Flatten recursion depth:** max depth 8; if exceeded, stop and warn.
|
||||||
|
- **Max value length:** 500 characters rendered inline; provide copy/full view for longer values.
|
||||||
|
- **Max JSON pretty print:** 1 MB rendered inline; above that show “download/copy only”.
|
||||||
|
|
||||||
|
### Graph Request Limits
|
||||||
|
- **Default Graph request timeout:** 30s per request.
|
||||||
|
- **Hydration pagination limit:** max 50 pages per subresource; if exceeded → warning + partial snapshot.
|
||||||
|
|
||||||
|
### Restore Safety
|
||||||
|
- **Dry-run is binary:** a restore run is either dry-run or execute; no “default dry-run=true” semantics.
|
||||||
|
- **Type mismatch gate:** `@odata.type` mismatch MUST block execution (preview may show).
|
||||||
|
|
||||||
|
### Retention / Housekeeping
|
||||||
|
- **Soft-deleted entities:** retained indefinitely unless explicitly force-deleted.
|
||||||
|
- **Audit logs:** retained indefinitely by default (configurable later).
|
||||||
|
|
||||||
|
### “Large payload” definition
|
||||||
|
- Any snapshot JSONB > 1 MB OR settings table > 1000 rows is considered **large** and triggers truncation rules above.
|
||||||
|
|
||||||
|
|
||||||
|
### FR-019 Settings Normalization & Display
|
||||||
|
|
||||||
|
FR-019.1 **Normalized Settings View**
|
||||||
|
- Admin can view a policy and policy version with settings rendered in a readable normalized format.
|
||||||
|
- The normalized output MUST hide Graph metadata keys unless explicitly requested.
|
||||||
|
|
||||||
|
FR-019.2 **Raw Snapshot + Copy**
|
||||||
|
- Admin can view raw JSON snapshot (pretty-printed where possible) and copy it.
|
||||||
|
|
||||||
|
FR-019.3 **Edge Handling**
|
||||||
|
- Malformed snapshots MUST show a warning banner and attempt partial rendering.
|
||||||
|
- `@odata.type` mismatch MUST show a warning; restore execution MUST be blocked.
|
||||||
|
|
||||||
|
FR-019.4 **Thresholds**
|
||||||
|
- Rendering and snapshot size limits MUST follow “Measurable Thresholds (NFR/UX)”.
|
||||||
|
|
||||||
|
|
||||||
|
- [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.
|
||||||
|
- Implements: FR-006, FR-008, FR-009
|
||||||
|
- Implements: FR-021
|
||||||
|
- Implements: FR-020
|
||||||
|
- Verified by: `./vendor/bin/pest tests/Feature/Filament/RestorePreviewTest.php tests/Feature/Filament/RestoreExecutionTest.php`
|
||||||
|
|
||||||
|
- [x] T145 [US1b] Create PolicyNormalizer service in `app/Services/Intune/PolicyNormalizer.php`.
|
||||||
|
- Implements: FR-019.1, FR-019.3
|
||||||
|
- Verified by: `./vendor/bin/pest tests/Unit/PolicyNormalizerTest.php`
|
||||||
|
|
||||||
|
- [x] T160 [US7] Add TenantResource ActionGroup entry “Setup Intune RBAC” …
|
||||||
|
- Implements: FR-023, FR-024
|
||||||
|
- Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`
|
||||||
|
|
||||||
|
- [x] T170 [US8] Add contract registry artifact …
|
||||||
|
- Implements: FR-031
|
||||||
|
- Verified by: `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php` (See <attachments> above for file contents. You may not need to search or read the file again.)
|
||||||
|
|
||||||
|
|
||||||
|
## 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/`.
|
||||||
|
- Implements: FR-013
|
||||||
|
- [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.
|
||||||
|
- [x] T176 [Scope][US1] Add Settings Catalog Policies as first-class type (`settingsCatalogPolicy`)
|
||||||
|
- **Goal**: Intune **Settings Catalog Policies** werden als **eigener Typ** synchronisiert, angezeigt und sind für Backup/Version/Diff/Preview/Restore (gemäß Matrix) korrekt routbar.
|
||||||
|
- **Why**: Settings Catalog Policies liegen in Graph unter `deviceManagement/configurationPolicies` (nicht unter `deviceManagement/deviceConfigurations`). Aktuell erscheinen sie daher nicht (oder nur unvollständig).
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
1) **Config: supported_types erweitern (Single Source of Truth)**
|
||||||
|
- In `config/tenantpilot.php` (oder eurem zentralen Type-Registry-File) neuen Typ hinzufügen:
|
||||||
|
- `key`: `settingsCatalogPolicy`
|
||||||
|
- `name`: `Settings Catalog Policy`
|
||||||
|
- `graph_resource`: `deviceManagement/configurationPolicies`
|
||||||
|
- `category`: `Configuration`
|
||||||
|
- `platform`: `windows` *(oder `all` + später per snapshot/@odata ableiten – je nach eurer Modelllogik)*
|
||||||
|
- UI-Label so wählen, dass Admin sofort erkennt: **“Settings Catalog”** (z. B. Badge/Label).
|
||||||
|
|
||||||
|
2) **Restore-Matrix erweitern**
|
||||||
|
- In eurer Restore-Konfig (`scope.restore_matrix` bzw. config-driven Matrix):
|
||||||
|
- `settingsCatalogPolicy: backup: full, restore: enabled, risk: medium` *(optional `medium-high` falls ihr strenger sein wollt)*
|
||||||
|
- Restore-Warnungen/Badges müssen den neuen Typ korrekt anzeigen.
|
||||||
|
|
||||||
|
3) **Graph Contract Registry erweitern**
|
||||||
|
- In `config/graph_contracts.php` Contract für `settingsCatalogPolicy` hinzufügen:
|
||||||
|
- Resource paths (collection + single item)
|
||||||
|
- `allowed_select`/`allowed_expand` (konservativ starten)
|
||||||
|
- `type_family` / erlaubte `@odata.type` Werte für diesen Typ
|
||||||
|
- Create/Update routing (`POST`/`PATCH` wie bei euren anderen Typen)
|
||||||
|
- Sicherstellen, dass **capability fallback** (downgrade ohne `$select/$expand`) auch hier greift.
|
||||||
|
|
||||||
|
4) **PolicySyncService erweitern**
|
||||||
|
- Sync-Pipeline muss zusätzlich `deviceManagement/configurationPolicies` abfragen und upserten:
|
||||||
|
- `policies.type_key = settingsCatalogPolicy`
|
||||||
|
- `external_id = Graph id`
|
||||||
|
- `display_name`, `description`, `last_modified`, etc.
|
||||||
|
- Tenant-scoping beibehalten.
|
||||||
|
- **No duplicates**: gleiche `external_id` darf nicht in zwei Typen landen (Unique/Guard prüfen).
|
||||||
|
|
||||||
|
5) **Snapshots / Settings availability**
|
||||||
|
- Für die Spalte/Badge **“Settings”** (Available/Missing):
|
||||||
|
- Snapshot-Fetch muss für `settingsCatalogPolicy` über den neuen Endpoint laufen (single item fetch).
|
||||||
|
- Normalizer/Validator:
|
||||||
|
- `@odata.type` muss für diesen Typ als kompatibel erkannt werden (über Contract/type-family).
|
||||||
|
|
||||||
|
6) **UI (Filament)**
|
||||||
|
- `PolicyResource`:
|
||||||
|
- Type/Category Filter um `Settings Catalog Policy` erweitern
|
||||||
|
- Optional: Category bleibt `Configuration`, aber Typ klar `Settings Catalog`
|
||||||
|
- Detailseite:
|
||||||
|
- Normalized Settings anzeigen (wenn euer Normalizer Settings Catalog schon kann)
|
||||||
|
- sonst mind.: **Raw JSON + Hinweis** “Settings Catalog normalization pending” (kein silent fail).
|
||||||
|
|
||||||
|
7) **Permissions/Health**
|
||||||
|
- Verify/Permissions-Liste prüfen, ob für `deviceManagement/configurationPolicies` zusätzliche Graph-Permissions nötig sind.
|
||||||
|
- Falls ja:
|
||||||
|
- `config/intune_permissions.php` ergänzen
|
||||||
|
- Health Panel zeigt fehlende Permission sauber an.
|
||||||
|
|
||||||
|
## Tests (Pest)
|
||||||
|
- **Unit**:
|
||||||
|
- Contract Registry erkennt `settingsCatalogPolicy`
|
||||||
|
- type-family ok (derived `@odata.type` accepted)
|
||||||
|
- fallback ok (capability downgrade)
|
||||||
|
- **Feature**:
|
||||||
|
- Policy Sync importiert `configurationPolicies` als `settingsCatalogPolicy` und listet sie in der UI
|
||||||
|
- Settings badge wird **Available**, sobald Snapshot vorhanden ist
|
||||||
|
- **Regression**:
|
||||||
|
- `deviceConfiguration` Sync bleibt unverändert (keine Vermischung)
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/PolicyListingTest.php`
|
||||||
|
- ggf. neue Tests:
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicySyncTest.php`
|
||||||
|
- Registry Tests erweitern:
|
||||||
|
- `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php`
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- In der Policies-Liste erscheinen Intune **Settings Catalog Policies** als eigener Typ **Settings Catalog Policy**.
|
||||||
|
- Admin kann danach **Backup/Version/Preview/Restore** (gemäß Matrix) für diesen Typ nutzen.
|
||||||
|
- **Keine Duplikate/Überlappung** mit `deviceConfiguration`.
|
||||||
|
- [x] T177 [US4][Bugfix] Settings Catalog Restore: Graph-Fehlerdetails speichern + PATCH-Payload sanitizen (contract-driven)
|
||||||
|
- **Goal**: Restore von `settingsCatalogPolicy` soll nicht mehr als generisches `400 Graph apply failed` enden, sondern:
|
||||||
|
1) echte Graph-Fehlerdetails persistieren + im UI sichtbar machen
|
||||||
|
2) beim PATCH nur ein zulässiges Payload senden (read-only/meta Felder raus, whitelist/contract-driven)
|
||||||
|
- **Why**: `deviceManagement/configurationPolicies` akzeptiert beim PATCH i. d. R. keinen vollständigen Snapshot → read-only Felder führen zu 400.
|
||||||
|
|
||||||
|
**Implementation**
|
||||||
|
1) **RestoreRun Results verbessern (Fehlerdetails persistieren)**
|
||||||
|
- In `RestoreService` (oder zentralem Graph-Apply Catch):
|
||||||
|
- Bei Graph-Exception zusätzlich in `restore_run_item_results`/`results` JSON speichern:
|
||||||
|
- `graph_error_code`
|
||||||
|
- `graph_error_message`
|
||||||
|
- optional (falls vorhanden): `graph_request_id`, `graph_client_request_id`, `graph_date`
|
||||||
|
- UI (RestoreRun Detail) soll bei failed Items neben `code/reason` auch `graph_error_message` anzeigen (kurz) + “Details” (expand/collapsible) für request ids.
|
||||||
|
|
||||||
|
2) **Contract Registry: update sanitizer für settingsCatalogPolicy**
|
||||||
|
- In `config/graph_contracts.php` bei `settingsCatalogPolicy` ergänzen:
|
||||||
|
- entweder `update_whitelist` (preferred) **oder** `update_strip_keys`
|
||||||
|
- `update_whitelist` konservativ starten (nur Felder, die PATCH typischerweise akzeptiert), z. B.:
|
||||||
|
- `name`, `description`, `settings`, `technologies`, `platforms`, `roleScopeTagIds`
|
||||||
|
- `assignments` **nur** wenn Restore wirklich Assignments patcht (sonst weglassen)
|
||||||
|
- In `GraphContractRegistry` (oder äquivalent) Methode bereitstellen:
|
||||||
|
- `sanitizeUpdatePayload(string $typeKey, array $snapshot): array`
|
||||||
|
- Entfernt immer: `id`, `createdDateTime`, `lastModifiedDateTime`, `@odata.*`, `version`, `roleScopeTagIds@odata.*`, sowie unbekannte Keys
|
||||||
|
- In `RestoreService` beim UPDATE/PATCH:
|
||||||
|
- für `settingsCatalogPolicy` vor dem Graph PATCH immer `sanitizeUpdatePayload()` verwenden.
|
||||||
|
|
||||||
|
3) **Graph apply: bessere Diagnose im Audit**
|
||||||
|
- Audit-Event (z. B. `restore.item.failed`) soll zusätzlich `graph_error_code` + `graph_request_id` enthalten (keine Tokens/payloads).
|
||||||
|
|
||||||
|
**Tests (Pest)**
|
||||||
|
- Unit: `GraphContractRegistry` sanitizer
|
||||||
|
- Given snapshot mit read-only/meta Feldern → sanitized payload enthält nur whitelist
|
||||||
|
- Feature: Restore execution für settingsCatalogPolicy mit “bad payload”
|
||||||
|
- Mock Graph 400 mit error body → RestoreRun result speichert `graph_error_message` + IDs
|
||||||
|
- UI assertion: Fehlermeldung sichtbar (kurz) + Details optional
|
||||||
|
|
||||||
|
**Verification**
|
||||||
|
- `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/RestoreExecutionTest.php` (oder neues `SettingsCatalogPolicyRestoreTest.php`)
|
||||||
|
- Manuell: RestoreRun detail zeigt bei 400 die echte Graph-Fehlermeldung + request-id; kein generisches “apply failed” ohne Details.
|
||||||
|
|
||||||
|
**Acceptance Criteria**
|
||||||
|
- Restore von `settingsCatalogPolicy` nutzt PATCH mit sanitiziertem Payload.
|
||||||
|
- Bei Fehlern ist im RestoreRun klar ersichtlich *warum* (Graph error message), inkl. request ids für Support.
|
||||||
|
|
||||||
|
- [x] T178 [US4][Bugfix] Settings Catalog Restore: PATCH strikt auf {name, description, settings} begrenzen + Property-Mapping (displayName→name) + case-insensitive strip
|
||||||
|
- **Problem**:
|
||||||
|
- Restore von `settingsCatalogPolicy` schlägt mit 400 fehl:
|
||||||
|
- “Invalid patch, attempting to patch property Platforms is not allowed. Valid properties are Name, Description, and Settings.”
|
||||||
|
- Sanitizer lässt `platforms/Platforms` noch durch und/oder es wird `displayName` statt `name` gepatcht.
|
||||||
|
- **Implementation**:
|
||||||
|
1) **Contract fix** (`config/graph_contracts.php`)
|
||||||
|
- Für `settingsCatalogPolicy` `update_whitelist` auf exakt:
|
||||||
|
- `name`, `description`, `settings`
|
||||||
|
- Optional: `update_map` definieren:
|
||||||
|
- `displayName` → `name`
|
||||||
|
- (und ggf. `Description`/`Settings` casing normalisieren)
|
||||||
|
2) **Sanitizer hardening** (`app/Services/Graph/GraphContractRegistry.php`)
|
||||||
|
- Whitelist/Strip **case-insensitive** anwenden (z. B. `Platforms`, `platforms`, `PlatformS` immer entfernen).
|
||||||
|
- Vor dem Final-Payload:
|
||||||
|
- Mapping anwenden (displayName→name)
|
||||||
|
- Blocklist zusätzlich hart erzwingen: `platforms`, `technologies`, `templateReference`, `id`, `@odata.*`, `createdDateTime`, `lastModifiedDateTime`
|
||||||
|
- Ergebnis-Payload für update muss **nur** `name/description/settings` enthalten.
|
||||||
|
3) **RestoreService** (`app/Services/Intune/RestoreService.php`)
|
||||||
|
- Sicherstellen, dass für `settingsCatalogPolicy` Update-Payload aus Sanitizer kommt (kein “merge back” später).
|
||||||
|
- Bei leerem Payload: als `noop`/`skipped` behandeln statt PATCH.
|
||||||
|
- **Tests (Pest)**:
|
||||||
|
- Unit: Sanitizer entfernt `platforms/Platforms` zuverlässig + mapping `displayName→name`:
|
||||||
|
- `tests/Unit/GraphContractRegistryTest.php` (erweitern)
|
||||||
|
- Feature: Restore Settings Catalog erzeugt PATCH ohne platforms und läuft durch (Graph mocked):
|
||||||
|
- `tests/Feature/Filament/SettingsCatalogRestoreTest.php` (happy-path ergänzen)
|
||||||
|
- **Verification**:
|
||||||
|
- `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php tests/Feature/Filament/SettingsCatalogRestoreTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
- **Acceptance**:
|
||||||
|
- Restore von `settingsCatalogPolicy` scheitert nicht mehr an `Platforms`.
|
||||||
|
- Results zeigen bei Fehlern weiterhin request-id/client-request-id (bleibt wie T177).
|
||||||
|
|
||||||
|
- [ ] T179 [US1b][Scope][settingsCatalogPolicy] Hydrate Settings Catalog “Configuration settings” for all snapshots (versions, backups, previews) and ensure normalized display.
|
||||||
|
|
||||||
|
- **Goal:** For `settingsCatalogPolicy`, the **Configuration settings** (as seen in the Intune Portal under *Configuration settings*) must be visible throughout the system. This includes:
|
||||||
|
- being part of the raw JSON in **Policy Versions** and **Backup Snapshots**.
|
||||||
|
- being displayed in the **Normalized settings** section in a readable list/table format.
|
||||||
|
- ensuring that **Diff, Preview, and Restore** operations are based on these detailed settings, not just on general metadata.
|
||||||
|
|
||||||
|
- **Why:** The base entity for `deviceManagement/configurationPolicies` often only provides metadata (`name`, `platforms`, `settingCount`, etc.). The actual settings reside in a subresource (e.g., `.../configurationPolicies/{id}/settings`). Without hydrating this data, TenantPilot cannot display or work with the most relevant policy details like PIN length or biometric settings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1) Centralize Snapshot Hydration
|
||||||
|
- In the service responsible for capturing snapshots (e.g., a central `PolicySnapshotService`, or within `VersionService` and `BackupService`), implement a method to hydrate `settingsCatalogPolicy` data.
|
||||||
|
- When the `type_key` is `settingsCatalogPolicy`:
|
||||||
|
1. `GET deviceManagement/configurationPolicies/{id}` (Base entity).
|
||||||
|
2. `GET deviceManagement/configurationPolicies/{id}/settings` (with proper paging).
|
||||||
|
3. Merge the retrieved settings into the snapshot under a consistent key (e.g., `snapshot['settings'] = [...]`).
|
||||||
|
- This hydration logic MUST be used for creating **policy versions**, **backup items**, and **restore previews**.
|
||||||
|
|
||||||
|
### 2) Enhance PolicyNormalizer
|
||||||
|
- In `app/Services/Intune/PolicyNormalizer.php`, ensure the normalizer can interpret and display the `snapshot['settings']` data for `settingsCatalogPolicy`.
|
||||||
|
- It should render a readable table/list of the settings, not just metadata.
|
||||||
|
|
||||||
|
### 3) Update UI Components
|
||||||
|
- Ensure the **Policy Detail** and **Policy Version Detail** pages use the hydrated snapshots to display the settings.
|
||||||
|
- The "Settings available" badge for `settingsCatalogPolicy` should only show "Available" if the snapshot contains the hydrated `settings`.
|
||||||
|
|
||||||
|
### 4) Testing
|
||||||
|
- **Feature Test:** Create a `SettingsCatalogPolicyHydrationTest.php` that:
|
||||||
|
- Mocks the Graph API for both the base entity and the `/settings` subresource.
|
||||||
|
- Triggers both a **Version Capture** and a **Backup**.
|
||||||
|
- Asserts that the resulting `PolicyVersion` and `BackupItem` snapshots contain the hydrated `settings`.
|
||||||
|
- Asserts that the Policy Detail and Version Detail pages display the normalized settings correctly.
|
||||||
|
- **Unit Test:** `PolicyNormalizerSettingsCatalogTest.php` should be updated to verify the rendering of a hydrated snapshot.
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicyHydrationTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
|
||||||
|
- [x] T180 - DUPLICATE of T179. Merged into T179.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [x] T182 [US1b][settingsCatalogPolicy][UX] Dynamic normalization of Settings Catalog “settings” (generic flatten + readable labels)
|
||||||
|
|
||||||
|
- **Goal:** `settingsCatalogPolicy` soll im **Normalized settings** Bereich nicht mehr nur “setting -” anzeigen, sondern die hydrierten `settings[]` **generisch** (ohne hartes Mapping pro Setting) als verständliche Liste/Tabelle darstellen:
|
||||||
|
- pro Setting: **SettingDefinitionId**, **Instance Type**, **Value** (und ggf. Choice-Value)
|
||||||
|
- nested `children` / group collections werden **rekursiv geflattet**
|
||||||
|
- optional: einfache Gruppierung (z. B. nach Prefix der definitionId oder “group root”)
|
||||||
|
- **Why:** Microsoft hat unzählige Settings. Wir brauchen eine **dynamische** Darstellung, die immer funktioniert – auch für neue Settings, ohne dass wir jedes Setting kennen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1) PolicyNormalizer: settingsCatalogPolicy → generic flatten
|
||||||
|
- In `app/Services/Intune/PolicyNormalizer.php`:
|
||||||
|
- Bei `policyType === settingsCatalogPolicy`:
|
||||||
|
- Wenn `snapshot['settings']` existiert:
|
||||||
|
- Erzeuge eine Normalizer-Sektion `Settings` als Tabelle/Repeatable:
|
||||||
|
- `definitionId` (string)
|
||||||
|
- `instanceType` (string, aus `settingInstance['@odata.type']`)
|
||||||
|
- `value` (string/number/bool/json; aus `simpleSettingValue.value` oder `choiceSettingValue.value`)
|
||||||
|
- `path` (optional): zusammengesetzter Pfad zur Einordnung (z. B. parentDefinitionId > childDefinitionId)
|
||||||
|
- Implementiere `flattenSettingsCatalogSettingInstances(array $settings): array`:
|
||||||
|
- Iteriere `settings[]` Einträge
|
||||||
|
- Extrahiere `settingInstance`
|
||||||
|
- Unterstütze generisch (mindestens):
|
||||||
|
- `deviceManagementConfigurationSimpleSettingInstance` → `simpleSettingValue.value`
|
||||||
|
- `deviceManagementConfigurationChoiceSettingInstance` → `choiceSettingValue.value`
|
||||||
|
- `deviceManagementConfigurationGroupSettingCollectionInstance`:
|
||||||
|
- iteriere `groupSettingCollectionValue[]`
|
||||||
|
- rekursiv `children[]`
|
||||||
|
- Fallback: wenn unbekannt → `value = json_encode(settingInstance)` (kurz/gekürzt)
|
||||||
|
- Für Rekursion: maximal Depth (z. B. 8) + Schutz gegen Zyklen/zu große Payloads.
|
||||||
|
- Optional: wenn Value ein “enum-like” String ist, zusätzlich `displayValue` = letzter Token nach `_` (nur für bessere Lesbarkeit, ohne Semantik zu behaupten).
|
||||||
|
- Wenn `settings` fehlt:
|
||||||
|
- Zeige Banner “Settings not hydrated” (oder “Partial snapshot”) und nur Metadaten.
|
||||||
|
|
||||||
|
### 2) Filament View: bessere Darstellung (Table statt “setting -”)
|
||||||
|
- In `PolicyResource/ViewPolicy` und `PolicyVersionResource/ViewPolicyVersion`:
|
||||||
|
- Stelle sicher, dass die Normalizer-Ausgabe für `Settings` als Tabelle angezeigt wird:
|
||||||
|
- Spalten: `Definition`, `Type`, `Value`, optional `Path`
|
||||||
|
- Lange Values: truncated mit “copy” möglich (oder expand/collapse).
|
||||||
|
|
||||||
|
### 3) Diff: Fokus auf echte Settings (optional, aber empfohlen)
|
||||||
|
- In der diff-summary Logik (falls vorhanden):
|
||||||
|
- Wenn `policyType=settingsCatalogPolicy` und `settings` vorhanden:
|
||||||
|
- Summary soll zumindest sagen: “X setting values changed/added/removed”
|
||||||
|
- (Die JSON diff bleibt weiterhin verfügbar.)
|
||||||
|
|
||||||
|
### 4) Performance & Safety
|
||||||
|
- Guardrails:
|
||||||
|
- max rows (z. B. 1000) → danach “truncated”
|
||||||
|
- value length max (z. B. 500 chars) → danach “truncated”
|
||||||
|
- depth limit
|
||||||
|
- Keine Secrets loggen; Normalizer arbeitet nur auf Snapshot JSONB.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tests (Pest)
|
||||||
|
|
||||||
|
### Unit: `tests/Unit/PolicyNormalizerSettingsCatalogFlattenTest.php`
|
||||||
|
- Input Snapshot mit:
|
||||||
|
- simpleSettingInstance (int)
|
||||||
|
- choiceSettingInstance (string)
|
||||||
|
- groupSettingCollectionInstance mit children (mix)
|
||||||
|
- Assert:
|
||||||
|
- Normalizer liefert `Settings` Sektion mit mehreren Zeilen
|
||||||
|
- jede Zeile hat `definitionId`, `instanceType`, `value`
|
||||||
|
- rekursive children werden als eigene Zeilen enthalten
|
||||||
|
- Unknown instance type fällt auf fallback (json string) ohne crash
|
||||||
|
|
||||||
|
### Feature: `tests/Feature/Filament/SettingsCatalogPolicyNormalizedDisplayTest.php`
|
||||||
|
- Erzeuge PolicyVersion (settingsCatalogPolicy) mit Snapshot inkl. `settings[]`
|
||||||
|
- Öffne Version-Detail und Policy-Detail
|
||||||
|
- Assert:
|
||||||
|
- In Normalized settings existiert Sektion “Settings”
|
||||||
|
- Tabelle enthält erwartete definitionIds und Werte (z. B. minimumpinlength=12, usebiometrics=true)
|
||||||
|
- Keine “setting -” Platzhalter mehr für diesen Snapshot
|
||||||
|
|
||||||
|
### Regression
|
||||||
|
- Bestehende Normalizer-Ausgaben für andere Typen bleiben unverändert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogFlattenTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicyNormalizedDisplayTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Settings Catalog Policy zeigt im **Normalized settings** Bereich eine verständliche **Settings-Tabelle** (DefinitionId/Type/Value) statt generischem “setting -”.
|
||||||
|
- Rekursive Group/Children-Settings werden sichtbar (nicht verloren).
|
||||||
|
- Darstellung ist **dynamisch** (kein hardcoded mapping pro Setting).
|
||||||
|
- Guardrails verhindern UI/Memory Explosion bei sehr großen Policies.
|
||||||
|
|
||||||
|
|
||||||
|
- [x] T183 [US1b][UX] Make Policy Version detail readable (Tabs + scroll-safe tables)
|
||||||
|
|
||||||
|
- **Goal:** Policy Version Detail (und optional Policy Detail) soll für Admins **lesbar** sein:
|
||||||
|
- **Normalized Settings** ist Default/primär sichtbar
|
||||||
|
- **Raw JSON** ist weiterhin verfügbar, aber UI zerbricht nicht durch riesige Payloads
|
||||||
|
- Settings Catalog Tabellen/Paths/IDs werden sauber dargestellt (kein “Textsalat”)
|
||||||
|
|
||||||
|
- **Why:** Aktuell verdrängt Raw JSON + lange SettingDefinitionIds/Paths die gesamte Seite. Admins sehen nicht mehr “was geändert wurde”, sondern nur Datenmüll.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1) UI Layout: Tabs (Normalized default, Raw JSON secondary)
|
||||||
|
- In `app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php` (und optional `ViewPolicy.php`)
|
||||||
|
- ersetze die aktuelle Darstellung durch **Tabs**:
|
||||||
|
- Tab 1: **Normalized settings** (Default)
|
||||||
|
- Tab 2: **Raw JSON** (mit Copy Button)
|
||||||
|
- optional Tab 3: **Diff** (falls vorhanden)
|
||||||
|
- Falls Filament Infolist-Komponenten keine Tabs erlauben:
|
||||||
|
- nutze eine `ViewEntry` und rendere Tabs in Blade via `x-filament::tabs`.
|
||||||
|
|
||||||
|
### 2) Raw JSON: Max height + scroll + monospace
|
||||||
|
- In der Raw JSON Blade-View (z.B. `resources/views/filament/infolists/entries/raw-json.blade.php` oder bestehende View)
|
||||||
|
- Wrap `<pre>` mit:
|
||||||
|
- `class="max-h-[520px] overflow-auto rounded-lg border bg-gray-50 p-3 text-xs font-mono leading-relaxed"`
|
||||||
|
- optional: “Expand” action (modal/slideOver) für Vollbildansicht.
|
||||||
|
|
||||||
|
### 3) Normalized settings tables: horizontal scroll + readable columns
|
||||||
|
- In `resources/views/filament/infolists/entries/normalized-settings.blade.php`
|
||||||
|
- Table container:
|
||||||
|
- `class="overflow-x-auto rounded-lg border"`
|
||||||
|
- Table:
|
||||||
|
- `class="min-w-[900px] table-fixed"`
|
||||||
|
- Cells:
|
||||||
|
- Definition/Path: `font-mono text-xs break-all whitespace-normal`
|
||||||
|
- Value: `break-words whitespace-normal`
|
||||||
|
- Column widths:
|
||||||
|
- Definition: `w-[35%]`, Type: `w-[20%]`, Value: `w-[25%]`, Path: `w-[20%]`
|
||||||
|
- Long values: clamp (optional):
|
||||||
|
- `line-clamp-2` + “Show more” (details/modal)
|
||||||
|
|
||||||
|
### 4) Optional: Search within Settings (nice-to-have)
|
||||||
|
- Add a small client-side filter input (Alpine) above settings table:
|
||||||
|
- filters rows by DefinitionId/Value/Path
|
||||||
|
- Keep it optional if you want minimal change in v1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tests (Pest)
|
||||||
|
|
||||||
|
### Feature: `tests/Feature/Filament/PolicyVersionReadableLayoutTest.php`
|
||||||
|
- Given a `settingsCatalogPolicy` version with long `settings` payload
|
||||||
|
- Assert:
|
||||||
|
- Tabs render (Normalized + Raw JSON)
|
||||||
|
- Raw JSON container has max-height/overflow classes
|
||||||
|
- Normalized table wrapper uses overflow-x
|
||||||
|
- Page does not contain extremely long unbroken lines without wrappers (basic assertion on classes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/PolicyVersionReadableLayoutTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Policy Version page is readable on normal screen widths:
|
||||||
|
- Normalized settings are immediately visible without scrolling past raw JSON
|
||||||
|
- Raw JSON is accessible in second tab and scrolls inside its container
|
||||||
|
- Settings table does not break layout; long IDs/paths wrap/scroll cleanly
|
||||||
|
- No regressions for other policy types (deviceConfiguration/compliance/scripts).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [x] T184 [US1b][UX] Use Filament Tables for Settings Catalog settings (Policy + Version) with responsive layout + SlideOver details
|
||||||
|
|
||||||
|
- **Goal:** `settingsCatalogPolicy` Settings sollen **lesbar, scannbar und bedienbar** sein:
|
||||||
|
- als echte **Filament Table** (nicht “pseudo table” im Infolist-Blade)
|
||||||
|
- mit **truncate + tooltip**, horizontal scroll, sticky header
|
||||||
|
- mit **Details** (SlideOver) + Copy pro Row
|
||||||
|
- identisch nutzbar in **Policy Detail** und **Policy Version Detail**
|
||||||
|
|
||||||
|
- **Why:** Die aktuelle Darstellung bricht Layout/Spaltenbreiten (Definition/Type/Value laufen ineinander). Filament Tables lösen genau diese Probleme (fixed layout, responsive, actions, search).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1) Introduce reusable Livewire component for Settings Catalog settings table
|
||||||
|
- New: `app/Livewire/SettingsCatalogSettingsTable.php`
|
||||||
|
- Props:
|
||||||
|
- `array $settingsRows` (aus PolicyNormalizer Output oder direkt aus Snapshot `settings`)
|
||||||
|
- `string $context` (`policy|version`) optional
|
||||||
|
- Intern: build a Filament `Table` with columns:
|
||||||
|
- **Definition** (`TextColumn::make('definition')`)
|
||||||
|
- `wrap(false)`, `searchable()`, `tooltip(fn($state) => $state)`, `limit(60)`
|
||||||
|
- **Type** (`TextColumn::make('type')`)
|
||||||
|
- `wrap(false)`, `toggleable()`, `limit(50)`, `tooltip(...)`
|
||||||
|
- **Value** (`TextColumn::make('value')`)
|
||||||
|
- `wrap(false)`, `limit(60)`, `tooltip(...)`
|
||||||
|
- render `(group)` badge for group rows
|
||||||
|
- **Path** (`TextColumn::make('path')`)
|
||||||
|
- `toggleable(isToggledHiddenByDefault: true)`, `limit(80)`, `tooltip(...)`
|
||||||
|
- Table config:
|
||||||
|
- `paginated([25, 50, 100])` (default 25)
|
||||||
|
- `searchPlaceholder('Search definition/value…')`
|
||||||
|
- `striped()`, `deferLoading()`
|
||||||
|
- Row Action:
|
||||||
|
- `Action::make('details')->label('Details')->icon('heroicon-m-eye')`
|
||||||
|
- opens **SlideOver**
|
||||||
|
- shows full Definition/Type/Value/Path + optional raw setting JSON (pretty)
|
||||||
|
- Copy buttons for Definition/Value
|
||||||
|
|
||||||
|
### 2) Embed component via ViewEntry in Policy + PolicyVersion detail
|
||||||
|
- Policy detail (`app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php`)
|
||||||
|
- For `settingsCatalogPolicy`:
|
||||||
|
- render `SettingsCatalogSettingsTable` (instead of current table block)
|
||||||
|
- pass rows from Normalizer (`normalize()` should expose a stable rows array)
|
||||||
|
- PolicyVersion detail (`app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php`)
|
||||||
|
- same embedding for `settingsCatalogPolicy`
|
||||||
|
|
||||||
|
> Rule: Nur für `settingsCatalogPolicy` auf Table UI umstellen. Andere Typen bleiben Infolist/KeyValue.
|
||||||
|
|
||||||
|
### 3) Tailwind/Filament styling guardrails (no layout break)
|
||||||
|
- Ensure table container is responsive:
|
||||||
|
- wrap table in `div class="overflow-x-auto"`
|
||||||
|
- set columns non-wrapping by default (truncate)
|
||||||
|
- Sticky header:
|
||||||
|
- enable sticky header in table (Filament supports sticky header via table wrapper CSS; if needed add a small CSS utility class in your Filament theme)
|
||||||
|
|
||||||
|
### 4) Normalizer output contract (stable)
|
||||||
|
- Ensure `PolicyNormalizer` returns for settingsCatalogPolicy:
|
||||||
|
- `['settings_table' => ['columns' => [...], 'rows' => [...]]]`
|
||||||
|
- rows fields: `definition`, `type`, `value`, `path`, `raw` (optional)
|
||||||
|
- Table uses **rows**, not parsing raw snapshot again (single source).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tests (Pest)
|
||||||
|
|
||||||
|
### Feature: `tests/Feature/Filament/SettingsCatalogSettingsTableRenderTest.php` (new)
|
||||||
|
- Create a policy + version with hydrated `settings`
|
||||||
|
- Visit Policy detail and PolicyVersion detail
|
||||||
|
- Assert:
|
||||||
|
- table headers visible (Definition/Type/Value)
|
||||||
|
- at least one known definition appears
|
||||||
|
- “Details” action exists
|
||||||
|
|
||||||
|
### Unit (optional): `tests/Unit/PolicyNormalizerSettingsCatalogRowsTest.php`
|
||||||
|
- Given snapshot with nested settings instances
|
||||||
|
- Assert normalizer returns rows with `definition/type/value/path`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `./vendor/bin/pest tests/Feature/Filament/SettingsCatalogSettingsTableRenderTest.php`
|
||||||
|
- `./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogRowsTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- In **Policy Detail** und **Policy Version Detail** sind Settings Catalog Settings als **Filament Table** sichtbar (lesbar, nicht überlappend).
|
||||||
|
- Lange Werte sind **truncated** aber per Tooltip/Details vollständig erreichbar.
|
||||||
|
- Pro Row gibt es **Details SlideOver** + Copy.
|
||||||
|
- Kein Layout-Bruch auf typischen Screenbreiten (Laptop/FullHD).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [ ]T185 [UX][US1b][settingsCatalogPolicy] Make Settings Catalog settings readable (label/value parsing + table ergonomics)
|
||||||
|
|
||||||
|
- **Goal:** Settings Catalog Policies sollen im Policy/Version Detail **für Admins lesbar** sein, ohne dass wir “alle Settings kennen müssen”.
|
||||||
|
- Tabelle zeigt **sprechende Bezeichnung** + **kompakte Werte**
|
||||||
|
- Lange IDs bleiben verfügbar (Tooltip/Copy/Details), aber dominieren nicht die UI
|
||||||
|
|
||||||
|
- **Why:** Aktuell sind `definitionId` und `choiceSettingValue.value` so lang, dass sie in der Tabelle abgeschnitten werden und der Admin weder Setting noch Wert versteht.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1) Presentation layer: generate human-friendly labels (no registry needed)
|
||||||
|
- Add helper in `PolicyNormalizer` (oder kleiner `SettingsCatalogPresenter`):
|
||||||
|
- `labelFromDefinitionId(string $definitionId): string`
|
||||||
|
- remove common prefixes: `device_vendor_msft_`, `user_vendor_msft_`, `policy_config_`, `admx_`
|
||||||
|
- replace `_` with spaces
|
||||||
|
- keep last 2–4 segments if string is huge
|
||||||
|
- replace `{tenantid}` with `{tenant}`
|
||||||
|
- Output example:
|
||||||
|
- `user_vendor_msft_policy_config_admx_desktop_nomydocumentsico...` → `Desktop: No My Documents Icon` (heuristic), fallback: last segments nicely spaced
|
||||||
|
|
||||||
|
> Heuristic only. If no good split possible, fallback to “last segments” label.
|
||||||
|
|
||||||
|
### 2) Parse values into a short “effective value”
|
||||||
|
- Implement `valuePreview(array $settingInstance): string`:
|
||||||
|
- For `SimpleSettingValue`: return scalar (`12`, `0`, `true/false`)
|
||||||
|
- For `ChoiceSettingValue.value`: return last token after last `_` OR map known boolean patterns:
|
||||||
|
- suffix `_true`/`_false` → `True`/`False`
|
||||||
|
- suffix `_0`/`_1` for allowed/blocked → show `0`/`1` but also tag `Allowed/Blocked` if detectable
|
||||||
|
- For group instances: show `(group)` and put children into details view only
|
||||||
|
|
||||||
|
### 3) Improve table ergonomics (Filament Table / Livewire)
|
||||||
|
- In `SettingsCatalogSettingsTable`:
|
||||||
|
- Columns:
|
||||||
|
1) **Setting** (human label) + small muted secondary line showing truncated definitionId
|
||||||
|
2) **Value** (valuePreview)
|
||||||
|
3) **Type** (badge: Choice/Simple/Group)
|
||||||
|
4) Optional: **Path** (toggleable, hidden by default)
|
||||||
|
- Add:
|
||||||
|
- `->searchable()` should search both label + raw definitionId + raw value
|
||||||
|
- `->wrap()` / `->limit()` for long strings
|
||||||
|
- tooltips showing full definitionId/value on hover
|
||||||
|
- “Copy” icon action in row details (SlideOver) for Definition + Raw JSON
|
||||||
|
- Ensure horizontal scroll only inside table container:
|
||||||
|
- wrapper `div` with `overflow-x-auto` + `max-w-full`
|
||||||
|
- table layout fixed where possible (`table-fixed`) to prevent column blowouts
|
||||||
|
|
||||||
|
### 4) Keep Raw JSON accessible but not primary
|
||||||
|
- In PolicyVersion view:
|
||||||
|
- Put Raw JSON into collapsible section or separate tab.
|
||||||
|
- Normalized Settings tab becomes default for settingsCatalogPolicy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tests (Pest)
|
||||||
|
|
||||||
|
### Unit: `tests/Unit/SettingsCatalogPresenterTest.php`
|
||||||
|
- labelFromDefinitionId() produces readable output and stable fallback
|
||||||
|
- valuePreview() returns expected previews for:
|
||||||
|
- choice true/false, numeric, group
|
||||||
|
|
||||||
|
### Feature: `tests/Feature/Filament/SettingsCatalogSettingsTableUsabilityTest.php`
|
||||||
|
- Render policy detail with one very long definition + long choice value
|
||||||
|
- Assert:
|
||||||
|
- label column shows shortened readable label (not the full raw string)
|
||||||
|
- value column shows preview (e.g., `True`, `12`, `Never`)
|
||||||
|
- details slide-over contains full raw definition/value + copy UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
- `./vendor/bin/pest tests/Unit/SettingsCatalogPresenterTest.php tests/Feature/Filament/SettingsCatalogSettingsTableUsabilityTest.php`
|
||||||
|
- `./vendor/bin/pint --dirty`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- In Policy Detail, Settings table shows:
|
||||||
|
- **Readable Setting name** (not a cut-off vendor string)
|
||||||
|
- **Readable Value preview** (True/False/12/etc.)
|
||||||
|
- Full raw definitionId and raw value remain accessible via tooltip and SlideOver + copy button.
|
||||||
|
- No layout overflow/broken columns on common laptop viewport widths.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 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).
|
||||||
|
- Implements: FR-001
|
||||||
|
- [x] T010 [US1] Create Filament resource/table for policies with filters and metadata columns in `app/Filament/Resources/PolicyResource.php`.
|
||||||
|
- Implements: FR-001
|
||||||
|
- [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`.
|
||||||
|
- Implements: FR-002
|
||||||
|
- Implements: FR-020
|
||||||
|
- [x] T014 [US2] Add Filament resource/pages for backup sets and items (list/detail) in `app/Filament/Resources/BackupSetResource.php`.
|
||||||
|
- Implements: FR-002
|
||||||
|
|
||||||
|
- [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`.
|
||||||
|
- Implements: FR-003
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
- Implements: FR-004
|
||||||
|
- [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`.
|
||||||
|
- Implements: FR-005
|
||||||
|
- [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`.
|
||||||
|
- Implements: FR-022
|
||||||
|
- [x] T025 [US4] Record restore run lifecycle (start, per-item result, completion) and audit events in `restore_runs` and `audit_logs`.
|
||||||
|
- Implements: FR-007
|
||||||
|
- [ ] T156 [US4][UX] Add “Rerun” action to RestoreRun row actions (ActionGroup): creates a new RestoreRun cloned from selected run (same backup_set_id, same selected items, same dry_run flag), enforces same safety gates/confirmations as original execution path, writes audit event restore_run.rerun_created with source_restore_run_id.
|
||||||
|
- Implements: FR-035
|
||||||
|
- Implements: FR-035
|
||||||
|
|
||||||
|
|
||||||
|
## Phase 7: User Story 5 - Operational readiness and environments (Priority: P2)
|
||||||
|
|
||||||
|
- [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`.
|
||||||
|
- Implements: FR-010
|
||||||
|
- Implements: FR-011
|
||||||
|
- Implements: FR-012
|
||||||
|
- [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: Tenant Management (Tenant hinzufügen, App-Setup, Verify) (Priority: P1)
|
||||||
|
|
||||||
|
> Hinweis: Diese Phase ist “Tenant Management” und **nicht** US6, damit US6 sauber “Permissions/Health” bleibt.
|
||||||
|
|
||||||
|
- [x] T030 [TENANT] Migration für `tenants` ergänzen/prüfen (name, tenant_id, domain, app_client_id, app_status, app_notes, timestamps).
|
||||||
|
- Implements: FR-011
|
||||||
|
- Implements: FR-014
|
||||||
|
- Implements: FR-015
|
||||||
|
- Implements: FR-016
|
||||||
|
- Implements: FR-017
|
||||||
|
- Implements: FR-018
|
||||||
|
- [x] T031 [TENANT] Eloquent Model `Tenant` (Beziehungen, tenant-aware scopes).
|
||||||
|
- [x] T032 [TENANT] Filament `TenantResource` (list/create/edit/detail; Actions: Open in Entra, Copy consent URL optional).
|
||||||
|
- [x] T033 [TENANT] `TenantConfigService` / Graph connectivity check.
|
||||||
|
- [x] T034 [TENANT] Action „Verify configuration“ + Audit (`tenant.config.verified`).
|
||||||
|
- [x] T035 [TENANT] Tenant-Kontext in Policy/Backup/Restore/Audit Services (tenant_id überall setzen).
|
||||||
|
- Implements: FR-009
|
||||||
|
- [x] T036 [TENANT] Feature-Test `TenantSetupTest` (ok/error + Audit).
|
||||||
|
- [x] T037 [TENANT] Admin-Consent Callback Route (state → tenant mapping, status update, UI page).
|
||||||
|
|
||||||
|
## Phase 9: User Story 6 - Berechtigungsübersicht & Health-Status (Priority: P1)
|
||||||
|
|
||||||
|
- [x] T040 [P] [US6] Zentrale Permissions-Liste `config/intune_permissions.php` (+ optional `docs/permissions.md`).
|
||||||
|
- Implements: FR-006
|
||||||
|
- Implements: FR-008
|
||||||
|
- [x] T041 [US6] Datenmodell Tenant-Berechtigungen (JSONB `granted_permissions` oder `tenant_permissions` Tabelle; status ok/missing/error).
|
||||||
|
- [x] T042 [US6] `TenantPermissionService` (required, granted, compare DTO).
|
||||||
|
- [x] T043 [US6] Tenant-Detail UI Panel „Permissions“ (required list + status).
|
||||||
|
- [x] T044 [US6] Verify erweitern: compare + persist + Audit `tenant.permissions.checked`.
|
||||||
|
- Implements: FR-006
|
||||||
|
- Implements: FR-008
|
||||||
|
- [x] T045 [US6] Tests: Unit compare + Feature Tenant detail status + Verify updates.
|
||||||
|
|
||||||
|
## Phase 9b: Scope-Ausrichtung auf neue Objekttypen
|
||||||
|
|
||||||
|
- [x] T028 [Scope] `config/tenantpilot.php` auf `scope.supported_types` erweitern; single source for sync/backup/restore.
|
||||||
|
- [x] T029 [Scope] Filament-UI an neue Typen anpassen (Filter/Grouping + Restore-Level Hinweise).
|
||||||
|
|
||||||
|
## Phase 10: Housekeeping – Delete-Funktionen für Backups & Versions
|
||||||
|
|
||||||
|
- [x] T060 [HK] BackupSets soft deletable + Guard gegen RestoreRuns.
|
||||||
|
- [x] T061 [HK] Filament Delete BackupSets + Confirmation + Audit (`backup.deleted`) + Guard.
|
||||||
|
- [x] T062 [HK] PolicyVersions soft deletable + Queries/Resources default non-deleted.
|
||||||
|
- [x] T063 [HK] Filament Delete PolicyVersions + Audit (`policy_version.deleted`).
|
||||||
|
- [x] T064 [HK] Tests Housekeeping (BackupSet delete ok/block + PolicyVersion delete).
|
||||||
|
|
||||||
|
## Phase 11: Housekeeping – Tenant löschen/deaktivieren
|
||||||
|
|
||||||
|
- [x] T070 [HK] Tenants soft deletable (optional status active/archived).
|
||||||
|
- [x] T071 [HK] Tenant deactivate/archive action + Audit (`tenant.archived`).
|
||||||
|
- [x] T072 [HK] Block operations for deactivated tenants (sync/backup/restore early fail).
|
||||||
|
- [x] T073 [HK] RestoreRuns soft deletable (optional) + Audit (`restore_run.deleted`).
|
||||||
|
- [x] T074 [HK] Tests Tenant delete/deactivate behavior + clear errors, no Graph calls.
|
||||||
|
|
||||||
|
## Phase 12: Housekeeping – Hard Deletes (Force Delete)
|
||||||
|
|
||||||
|
- [x] T075 [HK] Force-Delete-Actions (only in trashed; guards; audit before delete) + tests.
|
||||||
|
|
||||||
|
## Phase 12b: Single current tenant ("Highlander")
|
||||||
|
|
||||||
|
- [x] T120 [TENANT] Migration add `is_current` + partial unique index.
|
||||||
|
- [x] T121 [TENANT] Tenant::current() + makeCurrent() + remove implicit defaults.
|
||||||
|
- [x] T122 [TENANT] Data cleanup (mark one current; archive local-tenant).
|
||||||
|
- [x] T123 [TENANT] Filament UI badge + “Make current” action.
|
||||||
|
- [x] T124 [TENANT] Consumers refactor to `Tenant::current()` or explicit tenant.
|
||||||
|
- [x] T125 [TENANT] Tests for current selection + “Make current”.
|
||||||
|
|
||||||
|
- [x] T130 [UX] Tabellen-Aktionen in Dropdown bündeln (ActionGroup) in TenantResource (+ optional others).
|
||||||
|
|
||||||
|
## Phase 13: Settings Normalization & Display (Priority: P1)
|
||||||
|
|
||||||
|
- [x] T140 [P] [US1b] Unit test PolicyNormalizer.
|
||||||
|
- [x] T141 [P] [US1b] Feature test Policy Settings section.
|
||||||
|
- [x] T142 [P] [US1b] Feature test Version detail pretty JSON + normalized.
|
||||||
|
- [x] T143 [P] [Edge] Feature test malformed snapshot warning.
|
||||||
|
- [x] T144 [P] [Edge] Feature test @odata.type mismatch flag + restore exec block.
|
||||||
|
|
||||||
|
- [x] T145 [US1b] PolicyNormalizer service.
|
||||||
|
- Implements: FR-019
|
||||||
|
- [x] T146 [US1b] Settings infolist in PolicyResource.
|
||||||
|
- [x] T147 [US1b] PolicyVersion view pretty JSON + normalized.
|
||||||
|
- [x] T148 [US1b] Integrations (list badge, optional diff enhancements, tenant scoping).
|
||||||
|
- [x] T149 [Edge] SnapshotValidator helper.
|
||||||
|
- [x] T150 [Edge] @odata.type validator (policy/backup/restore gates).
|
||||||
|
- [x] T151 [Edge] UI warnings + restore execution gating (preview may show).
|
||||||
|
- [x] T152 [US1b] README docs for settings display.
|
||||||
|
- [x] T153 [US1b] Inline docs in PolicyNormalizer.
|
||||||
|
|
||||||
|
## Phase 14: User Story 7 – 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).
|
||||||
|
|
||||||
|
- [x] T160 [US7] Add TenantResource ActionGroup entry “Setup Intune RBAC”: visible for active tenants with `app_client_id`; sits alongside Admin consent/Verify; guarded by Highlander rules. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||||||
|
- Implements: FR-023
|
||||||
|
- Implements: FR-025
|
||||||
|
- Implements: FR-026
|
||||||
|
- Implements: FR-027
|
||||||
|
- Implements: FR-028
|
||||||
|
- Implements: FR-029
|
||||||
|
- [x] T161 [US7] Wizard UI (Filament): Step 1 preconditions/summary with inputs for Role, Scope, Group mode; least-privilege warnings; review screen of planned changes. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||||||
|
- [x] T162 [US7] Delegated auth step: initiate delegated login; stop with clear error + audit on failure; token not persisted. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||||||
|
- [x] T163 [US7] Execution service (sync) with audit per step: resolve SP by `app_client_id`; ensure/create security group (`securityEnabled=true`); add SP as member (idempotent); ensure/create/update Intune role assignment; persist IDs on tenant for idempotency. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||||||
|
- Implements: FR-030
|
||||||
|
|
||||||
|
- [x] T164 [US7] Post-check (mandatory): clear app token cache / force fresh token acquisition and run canary reads:
|
||||||
|
- `GET /deviceManagement/deviceConfigurations?$top=1`
|
||||||
|
- `GET /deviceManagement/deviceCompliancePolicies?$top=1`
|
||||||
|
- optional CA canary only if CA features enabled
|
||||||
|
- update tenant health + audit verify outcome.
|
||||||
|
Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||||||
|
|
||||||
|
- [x] T165 [US7] Tests (Pest, mocked Graph): happy path; rerun idempotent; missing permissions error mapping; scope-limited warning; delegated login failure path; non-security-enabled group failure. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||||||
|
- [x] T166 [US7] Documentation: README note for wizard behavior (delegated, sync), least-privilege defaults, audit expectations, rerun safety. Verified by: manual review of README.md update.
|
||||||
|
- [ ] T167 [US7-Optional] CLI/Job for CHECK/REPORT only (no grant), explicitly exclude async grant.
|
||||||
|
- [x] T168 [US7] Extend Verify configuration / Health panel to include “Intune RBAC status” (OK/Missing/Error) + CTA “Run Setup Intune RBAC”, persist last_checked_at + reason; Audit `tenant.rbac.checked`. Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||||||
|
- Implements: FR-024
|
||||||
|
- [x] T169 [US7] Persist RBAC artifacts on Tenant for idempotency:
|
||||||
|
- migration add nullable columns: `rbac_group_id`, `rbac_group_name`, `rbac_role_assignment_id`, `rbac_role_key`, `rbac_scope_mode`, `rbac_scope_id`
|
||||||
|
- prefer stored IDs on reruns; discovery fallback.
|
||||||
|
Verified by: `./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php`.
|
||||||
|
|
||||||
|
## Phase 15: User Story 8 – Graph Contract Registry & Drift Guard
|
||||||
|
|
||||||
|
**Scope**: FR-031 to FR-034; contract registry per type, type-family handling, capability fallbacks, drift checks.
|
||||||
|
|
||||||
|
- [x] T170 [US8] Add contract registry artifact (e.g., `config/graph_contracts.php`) capturing per supported type: resource paths, allowed `$select`/`$expand`, allowed @odata.type family, create/update methods, id field, hydration strategy. Verified by: manual review.
|
||||||
|
- [x] T171 [US8] Implement registry service + integration in Graph client to enforce allowed capabilities and downgrade on capability errors (retry without expand/select), logging warnings/audit entries. Verified by: `./vendor/bin/pest tests/Unit/GraphContractFallbackTest.php`.
|
||||||
|
- Implements: FR-032
|
||||||
|
- [x] T172 [US8] Implement type-family handling so derived @odata.type within a family routes correctly for preview/restore (no odata_mismatch) while still blocking unknown types. Verified by: `./vendor/bin/pest tests/Feature/Filament/ODataTypeMismatchTest.php`.
|
||||||
|
- Implements: FR-033
|
||||||
|
- [x] T173 [US8] Add verification command `php artisan graph:contract:check` (staging/CI) to probe endpoints, detect drift, and emit actionable diff/log output; make prod opt-in/guarded. Verified by: manual review.
|
||||||
|
- Implements: FR-034
|
||||||
|
- [x] T174 [US8] Tests (Pest/unit/integration): registry lookups, fallback selection on capability errors, derived type acceptance, drift-check command behavior. Verified by: `./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php tests/Unit/GraphContractFallbackTest.php`.
|
||||||
|
- [x] T175 [US8] Documentation: describe registry format/update process, fallback behavior, and how/when to run `graph:contract:check`. Verified by: manual review of README update.
|
||||||
Loading…
Reference in New Issue
Block a user