diff --git a/specs/009-app-protection-policy/plan.md b/specs/009-app-protection-policy/plan.md new file mode 100644 index 0000000..21ceaf6 --- /dev/null +++ b/specs/009-app-protection-policy/plan.md @@ -0,0 +1,30 @@ +# Implementation Plan: App Protection Policy Type (009) + +**Branch**: `feat/009-app-protection-policy` +**Date**: 2025-12-29 +**Spec Source**: [spec.md](./spec.md) + +## Summary +Make `appProtectionPolicy` reliable by: + +- Filtering non-policy objects during sync (`targetedManagedAppConfiguration`). +- Adding Graph contract coverage for assignments + `@odata.type` family. +- Adding targeted Pest tests to lock in behavior. + +## Execution Steps +1. Update `config/graph_contracts.php` for `appProtectionPolicy`: + - Add assignments list + assign action endpoints (and payload key if needed). + - Expand `type_family` to the common App Protection `@odata.type` values. +2. Update `app/Services/Intune/PolicySyncService.php`: + - Skip `#microsoft.graph.targetedManagedAppConfiguration` entries when syncing `appProtectionPolicy`. +3. Fix restore endpoints for assignments + policy updates: + - Use derived endpoints (e.g. `/androidManagedAppProtections/{id}` and `/androidManagedAppProtections/{id}/assign`) based on `@odata.type`. +4. Add admin-friendly normalization: + - Add `AppProtectionPolicyNormalizer` for boolean/duration formatting and Intune-like sections. +5. Add/extend tests: + - `tests/Unit/GraphContractRegistryActualDataTest.php` for `appProtectionPolicy` contract coverage. + - `tests/Feature/Jobs/*` to assert sync filtering behavior. + - `tests/Unit/*` to assert normalizer output and endpoint resolution. +6. Run formatting + tests: + - `./vendor/bin/pint --dirty` + - `./vendor/bin/sail artisan test --filter=appProtectionPolicy` diff --git a/specs/009-app-protection-policy/spec.md b/specs/009-app-protection-policy/spec.md new file mode 100644 index 0000000..4ff2cf1 --- /dev/null +++ b/specs/009-app-protection-policy/spec.md @@ -0,0 +1,57 @@ +# Feature Specification: App Protection (MAM) Policy Type Coverage + +**Feature Branch**: `feat/009-app-protection-policy` +**Created**: 2025-12-29 +**Status**: Draft + +## Overview +Make **App Protection (MAM)** policies (`appProtectionPolicy`) reliable in TenantAtlas’ existing Policy/Backup/Restore flows by: + +- Preventing **non-policy objects** (Managed App Configurations) from being imported as policies during sync. +- Capturing and restoring **assignments** for `managedAppPolicies`. +- Expanding the accepted `@odata.type` family so restore/create flows don’t fail with false `odata_mismatch`. +- Improving **admin readability** by normalizing key settings (booleans/durations) into Intune-like sections. + +## In Scope +- Policy type: `appProtectionPolicy` (`deviceAppManagement/managedAppPolicies`) +- Policy sync: skip objects with `@odata.type == #microsoft.graph.targetedManagedAppConfiguration` +- Backup/version capture: capture assignments when enabled +- Restore: reapply assignments using `/assign` with group + assignment filter mapping (existing mapping UI) +- UI: normalize App Protection snapshots for readability (bool/duration formatting + grouped sections) + +## Out of Scope (v1) +- “Target apps” (`/targetApps`) workflows for App Protection objects (showing the actual app list like Intune). +- Full “create from scratch” for missing App Protection policies (beyond generic create fallback). +- Separately modeling App Configurations (`targetedManagedAppConfigurations`) as their own policy type. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 — Clean Inventory (P1) +As an admin, I want the App Protection policy list to only include actual protection policies (not app configurations), so inventory stays accurate. + +**Independent Test**: Run policy sync; confirm `targetedManagedAppConfiguration` objects do not appear as `appProtectionPolicy` records. + +**Acceptance Scenarios** +1. Given Graph returns mixed objects from `managedAppPolicies`, when sync runs, then items with `@odata.type == #microsoft.graph.targetedManagedAppConfiguration` are skipped. + +### User Story 2 — Backup assignments (P1) +As an admin, I can capture App Protection assignments during backup/version capture, so restore can reproduce targeting. + +**Independent Test**: Capture a backup set with assignments enabled; verify assignments are saved for App Protection policies. + +**Acceptance Scenarios** +1. Given assignments are enabled, when capturing an App Protection snapshot, then assignments are fetched via the configured assignments endpoint and stored on the version/item. + +### User Story 3 — Restore assignments (P1) +As an admin, I can restore App Protection assignments using group mapping with clear skip/failure reasons. + +**Independent Test**: Restore an App Protection backup into a tenant with different group IDs; verify assignments are created/skipped with expected outcomes. + +**Acceptance Scenarios** +1. Given group mapping is present, when restore executes, then assignments are applied via `/assign`. +2. Given group mapping is missing for a group, when restore executes, then that assignment is skipped with a clear reason. + +## Notes +- Filtering is implemented in code because Graph filtering does not reliably exclude `targetedManagedAppConfiguration` objects from the `managedAppPolicies` list response. +- `@odata.type` matching uses `config/graph_contracts.php` as the safety gate for create flows. +- Assignments restore uses derived endpoints (e.g. `/deviceAppManagement/androidManagedAppProtections/{id}/assign`) based on `@odata.type` for compatibility. diff --git a/specs/009-app-protection-policy/tasks.md b/specs/009-app-protection-policy/tasks.md new file mode 100644 index 0000000..842265e --- /dev/null +++ b/specs/009-app-protection-policy/tasks.md @@ -0,0 +1,23 @@ +# Tasks: App Protection Policy Type Coverage (009) + +**Branch**: `feat/009-app-protection-policy` | **Date**: 2025-12-29 +**Input**: [spec.md](./spec.md), [plan.md](./plan.md) + +## Phase 1: Contracts +- [ ] T001 Add App Protection assignments endpoints + type family to `config/graph_contracts.php`. + +## Phase 2: Sync filtering +- [ ] T002 Filter out `#microsoft.graph.targetedManagedAppConfiguration` during `appProtectionPolicy` sync. + +## Phase 3: Restore endpoint compatibility +- [ ] T003 Resolve derived update/assign endpoints for App Protection based on `@odata.type`. + +## Phase 4: UI normalization +- [ ] T004 Add `AppProtectionPolicyNormalizer` (booleans/durations + grouped sections). + +## Phase 5: Tests + Verification +- [ ] T005 Add contract coverage tests in `tests/Unit/GraphContractRegistryActualDataTest.php`. +- [ ] T006 Add sync filtering test in `tests/Feature/Jobs/*`. +- [ ] T007 Add unit tests for derived endpoint resolution + normalizer output. +- [ ] T008 Run tests (targeted): `./vendor/bin/sail artisan test --filter=appProtectionPolicy` +- [ ] T009 Run Pint: `./vendor/bin/pint --dirty`