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,
|
'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') {
|
if ($policy->policy_type === 'deviceCompliancePolicy') {
|
||||||
$options['expand'] = 'scheduledActionsForRule($expand=scheduledActionConfigurations)';
|
$options['expand'] = 'scheduledActionsForRule($expand=scheduledActionConfigurations)';
|
||||||
}
|
}
|
||||||
@ -110,6 +118,10 @@ public function fetch(Tenant $tenant, Policy $policy, ?string $actorEmail = null
|
|||||||
$metadataWarnings = $response->warnings ?? [$reason];
|
$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 : []);
|
$validation = $this->snapshotValidator->validate(is_array($payload) ? $payload : []);
|
||||||
$metadataWarnings = array_merge($metadataWarnings, $validation['warnings']);
|
$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.
|
* Hydrate settings catalog policies with configuration settings subresource.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -323,11 +323,50 @@
|
|||||||
'allowed_expand' => [],
|
'allowed_expand' => [],
|
||||||
'type_family' => [
|
'type_family' => [
|
||||||
'#microsoft.graph.mobileApp',
|
'#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',
|
'create_method' => 'POST',
|
||||||
'update_method' => 'PATCH',
|
'update_method' => 'PATCH',
|
||||||
'id_field' => 'id',
|
'id_field' => 'id',
|
||||||
'hydration' => 'properties',
|
'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' => [
|
'assignmentFilter' => [
|
||||||
'resource' => 'deviceManagement/assignmentFilters',
|
'resource' => 'deviceManagement/assignmentFilters',
|
||||||
|
|||||||
@ -102,6 +102,20 @@
|
|||||||
->toContain('scheduledActionsForRule($expand=scheduledActionConfigurations)');
|
->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 () {
|
it('omits role scope tags from assignment filter selects', function () {
|
||||||
$contract = $this->registry->get('assignmentFilter');
|
$contract = $this->registry->get('assignmentFilter');
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,22 @@ public function getPolicy(string $policyType, string $policyId, array $options =
|
|||||||
{
|
{
|
||||||
$this->requests[] = ['getPolicy', $policyType, $policyId, $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: [
|
return new GraphResponse(success: true, data: [
|
||||||
'payload' => [
|
'payload' => [
|
||||||
'id' => $policyId,
|
'id' => $policyId,
|
||||||
@ -107,3 +123,45 @@ public function request(string $method, string $path, array $options = []): Grap
|
|||||||
expect($client->requests[0][3]['expand'] ?? null)
|
expect($client->requests[0][3]['expand'] ?? null)
|
||||||
->toBe('scheduledActionsForRule($expand=scheduledActionConfigurations)');
|
->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