feat: mobileApp metadata-only + assignment restore
This commit is contained in:
parent
a477ebce49
commit
623d6904a0
@ -46,6 +46,14 @@ public function fetch(Tenant $tenant, Policy $policy, ?string $actorEmail = null
|
||||
'platform' => $policy->platform,
|
||||
];
|
||||
|
||||
if ($this->isMetadataOnlyPolicyType($policy->policy_type)) {
|
||||
$select = $this->metadataOnlySelect($policy->policy_type);
|
||||
|
||||
if ($select !== []) {
|
||||
$options['select'] = $select;
|
||||
}
|
||||
}
|
||||
|
||||
if ($policy->policy_type === 'deviceCompliancePolicy') {
|
||||
$options['expand'] = 'scheduledActionsForRule($expand=scheduledActionConfigurations)';
|
||||
}
|
||||
@ -110,6 +118,10 @@ public function fetch(Tenant $tenant, Policy $policy, ?string $actorEmail = null
|
||||
$metadataWarnings = $response->warnings ?? [$reason];
|
||||
}
|
||||
|
||||
if (! $response->failed() && $this->isMetadataOnlyPolicyType($policy->policy_type)) {
|
||||
$payload = $this->filterMetadataOnlyPayload($policy->policy_type, is_array($payload) ? $payload : []);
|
||||
}
|
||||
|
||||
$validation = $this->snapshotValidator->validate(is_array($payload) ? $payload : []);
|
||||
$metadataWarnings = array_merge($metadataWarnings, $validation['warnings']);
|
||||
|
||||
@ -130,6 +142,55 @@ public function fetch(Tenant $tenant, Policy $policy, ?string $actorEmail = null
|
||||
];
|
||||
}
|
||||
|
||||
private function isMetadataOnlyPolicyType(string $policyType): bool
|
||||
{
|
||||
foreach (config('tenantpilot.supported_policy_types', []) as $type) {
|
||||
if (($type['type'] ?? null) === $policyType) {
|
||||
return ($type['backup'] ?? null) === 'metadata-only';
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function metadataOnlySelect(string $policyType): array
|
||||
{
|
||||
$contract = $this->contracts->get($policyType);
|
||||
$allowedSelect = $contract['allowed_select'] ?? [];
|
||||
|
||||
if (! is_array($allowedSelect)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_filter(
|
||||
$allowedSelect,
|
||||
static fn (mixed $key) => is_string($key) && $key !== '@odata.type'
|
||||
));
|
||||
}
|
||||
|
||||
private function filterMetadataOnlyPayload(string $policyType, array $payload): array
|
||||
{
|
||||
$contract = $this->contracts->get($policyType);
|
||||
$allowedSelect = $contract['allowed_select'] ?? [];
|
||||
|
||||
if (! is_array($allowedSelect) || $allowedSelect === []) {
|
||||
return $payload;
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
|
||||
foreach ($allowedSelect as $key) {
|
||||
if (is_string($key) && array_key_exists($key, $payload)) {
|
||||
$filtered[$key] = $payload[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate settings catalog policies with configuration settings subresource.
|
||||
*
|
||||
|
||||
@ -323,11 +323,50 @@
|
||||
'allowed_expand' => [],
|
||||
'type_family' => [
|
||||
'#microsoft.graph.mobileApp',
|
||||
'#microsoft.graph.androidLobApp',
|
||||
'#microsoft.graph.androidStoreApp',
|
||||
'#microsoft.graph.androidManagedStoreApp',
|
||||
'#microsoft.graph.iosLobApp',
|
||||
'#microsoft.graph.iosStoreApp',
|
||||
'#microsoft.graph.iosVppApp',
|
||||
'#microsoft.graph.winGetApp',
|
||||
'#microsoft.graph.macOSLobApp',
|
||||
'#microsoft.graph.macOSMicrosoftEdgeApp',
|
||||
'#microsoft.graph.macOSMicrosoftDefenderApp',
|
||||
'#microsoft.graph.macOSDmgApp',
|
||||
'#microsoft.graph.macOSPkgApp',
|
||||
'#microsoft.graph.macOsVppApp',
|
||||
'#microsoft.graph.macOSWebClip',
|
||||
'#microsoft.graph.managedAndroidLobApp',
|
||||
'#microsoft.graph.managedAndroidStoreApp',
|
||||
'#microsoft.graph.managedIOSLobApp',
|
||||
'#microsoft.graph.managedIOSStoreApp',
|
||||
'#microsoft.graph.microsoftStoreForBusinessApp',
|
||||
'#microsoft.graph.officeSuiteApp',
|
||||
'#microsoft.graph.macOSOfficeSuiteApp',
|
||||
'#microsoft.graph.webApp',
|
||||
'#microsoft.graph.windowsWebApp',
|
||||
'#microsoft.graph.windowsAppX',
|
||||
'#microsoft.graph.windowsUniversalAppX',
|
||||
'#microsoft.graph.windowsMicrosoftEdgeApp',
|
||||
'#microsoft.graph.windowsMobileMSI',
|
||||
'#microsoft.graph.windowsPhone81AppXBundle',
|
||||
'#microsoft.graph.windowsPhone81AppX',
|
||||
'#microsoft.graph.windowsPhone81StoreApp',
|
||||
'#microsoft.graph.windowsPhoneXAP',
|
||||
'#microsoft.graph.windowsStoreApp',
|
||||
'#microsoft.graph.win32LobApp',
|
||||
'#microsoft.graph.win32CatalogApp',
|
||||
'#microsoft.graph.iOSiPadOSWebClip',
|
||||
],
|
||||
'create_method' => 'POST',
|
||||
'update_method' => 'PATCH',
|
||||
'id_field' => 'id',
|
||||
'hydration' => 'properties',
|
||||
'assignments_list_path' => '/deviceAppManagement/mobileApps/{id}/assignments',
|
||||
'assignments_create_path' => '/deviceAppManagement/mobileApps/{id}/assign',
|
||||
'assignments_create_method' => 'POST',
|
||||
'assignments_payload_key' => 'mobileAppAssignments',
|
||||
],
|
||||
'assignmentFilter' => [
|
||||
'resource' => 'deviceManagement/assignmentFilters',
|
||||
|
||||
@ -102,6 +102,20 @@
|
||||
->toContain('scheduledActionsForRule($expand=scheduledActionConfigurations)');
|
||||
});
|
||||
|
||||
it('exposes mobile app assignment endpoints and type family', function () {
|
||||
$contract = $this->registry->get('mobileApp');
|
||||
|
||||
expect($contract)->not->toBeEmpty();
|
||||
expect($contract['assignments_list_path'] ?? null)
|
||||
->toBe('/deviceAppManagement/mobileApps/{id}/assignments');
|
||||
expect($contract['assignments_create_path'] ?? null)
|
||||
->toBe('/deviceAppManagement/mobileApps/{id}/assign');
|
||||
expect($contract['assignments_payload_key'] ?? null)
|
||||
->toBe('mobileAppAssignments');
|
||||
expect($this->registry->matchesTypeFamily('mobileApp', '#microsoft.graph.win32LobApp'))->toBeTrue();
|
||||
expect($this->registry->matchesTypeFamily('mobileApp', '#microsoft.graph.iosVppApp'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('omits role scope tags from assignment filter selects', function () {
|
||||
$contract = $this->registry->get('assignmentFilter');
|
||||
|
||||
|
||||
@ -24,6 +24,22 @@ public function getPolicy(string $policyType, string $policyId, array $options =
|
||||
{
|
||||
$this->requests[] = ['getPolicy', $policyType, $policyId, $options];
|
||||
|
||||
if ($policyType === 'mobileApp') {
|
||||
return new GraphResponse(success: true, data: [
|
||||
'payload' => [
|
||||
'id' => $policyId,
|
||||
'displayName' => 'Contoso Portal',
|
||||
'publisher' => 'Contoso',
|
||||
'description' => 'Company Portal',
|
||||
'@odata.type' => '#microsoft.graph.win32LobApp',
|
||||
'createdDateTime' => '2025-01-01T00:00:00Z',
|
||||
'lastModifiedDateTime' => '2025-01-02T00:00:00Z',
|
||||
'installCommandLine' => 'setup.exe /quiet',
|
||||
'largeIcon' => ['type' => 'image/png', 'value' => '...'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return new GraphResponse(success: true, data: [
|
||||
'payload' => [
|
||||
'id' => $policyId,
|
||||
@ -107,3 +123,45 @@ public function request(string $method, string $path, array $options = []): Grap
|
||||
expect($client->requests[0][3]['expand'] ?? null)
|
||||
->toBe('scheduledActionsForRule($expand=scheduledActionConfigurations)');
|
||||
});
|
||||
|
||||
it('filters mobile app snapshots to metadata-only keys', function () {
|
||||
$client = new PolicySnapshotGraphClient;
|
||||
app()->instance(GraphClientInterface::class, $client);
|
||||
|
||||
$tenant = Tenant::factory()->create([
|
||||
'tenant_id' => 'tenant-apps',
|
||||
'app_client_id' => 'client-123',
|
||||
'app_client_secret' => 'secret-123',
|
||||
'is_current' => true,
|
||||
]);
|
||||
$tenant->makeCurrent();
|
||||
|
||||
$policy = Policy::factory()->create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'external_id' => 'app-123',
|
||||
'policy_type' => 'mobileApp',
|
||||
'display_name' => 'Contoso Portal',
|
||||
'platform' => 'all',
|
||||
]);
|
||||
|
||||
$service = app(PolicySnapshotService::class);
|
||||
$result = $service->fetch($tenant, $policy);
|
||||
|
||||
expect($result['payload'])->toHaveKeys([
|
||||
'id',
|
||||
'displayName',
|
||||
'publisher',
|
||||
'description',
|
||||
'@odata.type',
|
||||
'createdDateTime',
|
||||
'lastModifiedDateTime',
|
||||
]);
|
||||
expect($result['payload'])->not->toHaveKey('installCommandLine');
|
||||
expect($result['payload'])->not->toHaveKey('largeIcon');
|
||||
expect($client->requests[0][0])->toBe('getPolicy');
|
||||
expect($client->requests[0][1])->toBe('mobileApp');
|
||||
expect($client->requests[0][2])->toBe('app-123');
|
||||
expect($client->requests[0][3]['select'] ?? null)->toBeArray();
|
||||
expect($client->requests[0][3]['select'])->toContain('displayName');
|
||||
expect($client->requests[0][3]['select'])->not->toContain('@odata.type');
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user