From cf1cd04740bd7e06a65913bbe1e4efa08743d03b Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Mon, 29 Dec 2025 23:59:47 +0100 Subject: [PATCH] fix: persist scope tags on restore versions --- app/Services/Intune/RestoreService.php | 131 ++++++++++++++++++ .../Feature/Filament/RestoreExecutionTest.php | 16 ++- 2 files changed, 146 insertions(+), 1 deletion(-) diff --git a/app/Services/Intune/RestoreService.php b/app/Services/Intune/RestoreService.php index efa9ee7..66b0731 100644 --- a/app/Services/Intune/RestoreService.php +++ b/app/Services/Intune/RestoreService.php @@ -151,6 +151,7 @@ public function execute( $foundationSkipped = (int) ($foundationOutcome['skipped'] ?? 0); $foundationMappingByType = $this->buildFoundationMappingByType($foundationEntries); $scopeTagMapping = $foundationMappingByType['roleScopeTag'] ?? []; + $scopeTagNamesById = $this->buildScopeTagNameLookup($foundationEntries); if (! $dryRun) { $this->auditFoundationMapping( @@ -610,6 +611,13 @@ public function execute( ->first(); if ($policy && $itemStatus === 'applied') { + $scopeTagsForVersion = $this->buildScopeTagsForVersion( + scopeTagIds: $mappedScopeTagIds ?? null, + backupItemMetadata: $item->metadata ?? [], + scopeTagMapping: $scopeTagMapping, + scopeTagNamesById: $scopeTagNamesById, + ); + $this->versionService->captureVersion( policy: $policy, payload: $item->payload, @@ -620,6 +628,7 @@ public function execute( 'backup_item_id' => $item->id, ], assignments: $restoredAssignments, + scopeTags: $scopeTagsForVersion, ); } } @@ -2192,6 +2201,128 @@ private function sanitizeGroupPolicyDefinitionValue(array $definitionValue): arr return $clean; } + /** + * @param array> $foundationEntries + * @return array + */ + private function buildScopeTagNameLookup(array $foundationEntries): array + { + $names = []; + + foreach ($foundationEntries as $entry) { + if (! is_array($entry)) { + continue; + } + + if (($entry['type'] ?? null) !== 'roleScopeTag') { + continue; + } + + $targetId = $entry['targetId'] ?? null; + $targetName = $entry['targetName'] ?? null; + + if (! is_string($targetId) || $targetId === '') { + continue; + } + + if (! is_string($targetName) || $targetName === '') { + continue; + } + + $names[$targetId] = $targetName; + } + + return $names; + } + + /** + * @param array|null $scopeTagIds + * @param array $backupItemMetadata + * @param array $scopeTagMapping + * @param array $scopeTagNamesById + * @return array{ids: array, names: array}|null + */ + private function buildScopeTagsForVersion( + ?array $scopeTagIds, + array $backupItemMetadata, + array $scopeTagMapping, + array $scopeTagNamesById, + ): ?array { + if ($scopeTagIds === null) { + return null; + } + + $ids = []; + + foreach ($scopeTagIds as $id) { + if (! is_string($id) && ! is_int($id)) { + continue; + } + + $id = (string) $id; + + if ($id === '') { + continue; + } + + $ids[] = $id; + } + + $ids = array_values(array_unique($ids)); + + if ($ids === []) { + return null; + } + + $namesById = $scopeTagNamesById; + + $metaScopeTagIds = $backupItemMetadata['scope_tag_ids'] ?? null; + $metaScopeTagNames = $backupItemMetadata['scope_tag_names'] ?? null; + + if (is_array($metaScopeTagIds) && is_array($metaScopeTagNames)) { + foreach ($metaScopeTagIds as $index => $sourceId) { + if (! is_string($sourceId) && ! is_int($sourceId)) { + continue; + } + + $sourceId = (string) $sourceId; + + if ($sourceId === '') { + continue; + } + + $name = $metaScopeTagNames[$index] ?? null; + + if (! is_string($name) || $name === '') { + continue; + } + + $targetId = $scopeTagMapping[$sourceId] ?? $sourceId; + + if ($targetId !== '' && ! array_key_exists($targetId, $namesById)) { + $namesById[$targetId] = $name; + } + } + } + + $names = []; + + foreach ($ids as $id) { + if ($id === '0') { + $names[] = 'Default'; + + continue; + } + + $names[] = $namesById[$id] ?? "Unknown (ID: {$id})"; + } + + return [ + 'ids' => $ids, + 'names' => $names, + ]; + } + private function assertActiveContext(Tenant $tenant, BackupSet $backupSet): void { if (! $tenant->isActive()) { diff --git a/tests/Feature/Filament/RestoreExecutionTest.php b/tests/Feature/Filament/RestoreExecutionTest.php index 590049c..60a4cba 100644 --- a/tests/Feature/Filament/RestoreExecutionTest.php +++ b/tests/Feature/Filament/RestoreExecutionTest.php @@ -77,7 +77,14 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon 'policy_identifier' => $policy->external_id, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, - 'payload' => ['foo' => 'bar'], + 'payload' => [ + 'foo' => 'bar', + 'roleScopeTagIds' => ['0', 'scope-1'], + ], + 'metadata' => [ + 'scope_tag_ids' => ['0', 'scope-1'], + 'scope_tag_names' => ['Default', 'Verbund-1'], + ], ]); $user = User::factory()->create(['email' => 'tester@example.com']); @@ -102,6 +109,13 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon ]); expect(PolicyVersion::where('policy_id', $policy->id)->count())->toBe(1); + + $version = PolicyVersion::where('policy_id', $policy->id)->first(); + expect($version)->not->toBeNull(); + expect($version->scope_tags)->toBe([ + 'ids' => ['0', 'scope-1'], + 'names' => ['Default', 'Verbund-1'], + ]); }); test('restore execution records foundation mappings', function () {