From 8a54a80fa9dfc9806cfd114e1a0551f039f49f35 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Mon, 29 Dec 2025 14:39:33 +0100 Subject: [PATCH] fix: restore scope tags without mapping --- app/Services/Intune/RestoreService.php | 25 ++++- .../Feature/Filament/RestoreExecutionTest.php | 101 ++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/app/Services/Intune/RestoreService.php b/app/Services/Intune/RestoreService.php index 04d729c..3b96dc0 100644 --- a/app/Services/Intune/RestoreService.php +++ b/app/Services/Intune/RestoreService.php @@ -794,11 +794,32 @@ private function applyScopeTagMapping(array $payload, array $scopeTagMapping): a */ private function applyScopeTagIdsToPayload(array $payload, ?array $scopeTagIds, array $scopeTagMapping): array { - if ($scopeTagIds === null || $scopeTagMapping === []) { + if ($scopeTagIds === null) { return $payload; } - $payload['roleScopeTagIds'] = array_values($scopeTagIds); + $mapped = []; + + foreach ($scopeTagIds as $id) { + if (! is_string($id) && ! is_int($id)) { + continue; + } + + $stringId = (string) $id; + + if ($stringId === '') { + continue; + } + + $mapped[] = $scopeTagMapping[$stringId] ?? $stringId; + } + + if ($mapped === []) { + return $payload; + } + + $payload['roleScopeTagIds'] = array_values(array_unique($mapped)); + unset($payload['RoleScopeTagIds']); return $payload; } diff --git a/tests/Feature/Filament/RestoreExecutionTest.php b/tests/Feature/Filament/RestoreExecutionTest.php index 7e873b7..590049c 100644 --- a/tests/Feature/Filament/RestoreExecutionTest.php +++ b/tests/Feature/Filament/RestoreExecutionTest.php @@ -283,6 +283,107 @@ public function getServicePrincipalPermissions(array $options = []): GraphRespon ]); }); +test('restore execution applies scope tags even without foundation mapping', function () { + $client = new class implements GraphClientInterface + { + public array $applied = []; + + public function listPolicies(string $policyType, array $options = []): GraphResponse + { + return new GraphResponse(true, []); + } + + public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse + { + return new GraphResponse(true, ['payload' => []]); + } + + public function getOrganization(array $options = []): GraphResponse + { + return new GraphResponse(true, []); + } + + public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse + { + $this->applied[] = [ + 'policyType' => $policyType, + 'policyId' => $policyId, + 'payload' => $payload, + 'options' => $options, + ]; + + return new GraphResponse(true, []); + } + + public function request(string $method, string $path, array $options = []): GraphResponse + { + return new GraphResponse(true, []); + } + + public function getServicePrincipalPermissions(array $options = []): GraphResponse + { + return new GraphResponse(true, []); + } + }; + + app()->instance(GraphClientInterface::class, $client); + + $tenant = Tenant::factory()->create([ + 'tenant_id' => 'tenant-scope-tags', + 'name' => 'Tenant Scope Tags', + 'status' => 'active', + 'metadata' => [], + ]); + + $policy = Policy::create([ + 'tenant_id' => $tenant->id, + 'external_id' => 'app-1', + 'policy_type' => 'mobileApp', + 'display_name' => 'Mozilla Firefox', + 'platform' => 'all', + ]); + + $backupSet = BackupSet::create([ + 'tenant_id' => $tenant->id, + 'name' => 'Backup', + 'status' => 'completed', + 'item_count' => 1, + ]); + + $backupItem = BackupItem::create([ + 'tenant_id' => $tenant->id, + 'backup_set_id' => $backupSet->id, + 'policy_id' => $policy->id, + 'policy_identifier' => $policy->external_id, + 'policy_type' => $policy->policy_type, + 'platform' => $policy->platform, + 'payload' => [ + '@odata.type' => '#microsoft.graph.winGetApp', + 'displayName' => 'Mozilla Firefox', + 'roleScopeTagIds' => ['0', 'tag-1'], + ], + ]); + + $user = User::factory()->create(['email' => 'tester@example.com']); + $this->actingAs($user); + + $service = app(RestoreService::class); + $run = $service->execute( + tenant: $tenant, + backupSet: $backupSet, + selectedItemIds: [$backupItem->id], + dryRun: false, + actorEmail: $user->email, + actorName: $user->name, + ); + + expect($run->status)->toBe('completed'); + expect($client->applied)->toHaveCount(1); + expect($client->applied[0]['policyType'])->toBe('mobileApp'); + expect($client->applied[0]['policyId'])->toBe('app-1'); + expect($client->applied[0]['payload']['roleScopeTagIds'])->toBe(['0', 'tag-1']); +}); + test('restore execution creates an autopilot profile when missing', function () { $graphClient = new class implements GraphClientInterface {