expose enrollment config subtypes as their own policy types (limit/platform restrictions/notifications) with preview-only restore risk and proper Graph contracts classify enrollment configs by their @odata.type + deviceEnrollmentConfigurationType so sync only keeps ESP in windowsEnrollmentStatusPage and the rest stay in their own types, including new restore-normalizer UI blocks + warnings hydrate enrollment notifications: snapshot fetch now downloads each notification template + localized messages, normalized view surfaces template names/subjects/messages, and restore previews keep preview-only behavior tenant UI tweaks: Tenant list and detail actions moved into an action group; “Open in Entra” re-added in index, and detail now has “Deactivate” + tests covering the new menu layout and actions tests added/updated for sync, snapshots, restores, normalized settings, tenant UI, plus Pint/test suite run Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #31
251 lines
8.9 KiB
PHP
251 lines
8.9 KiB
PHP
<?php
|
|
|
|
use App\Models\Policy;
|
|
use App\Models\Tenant;
|
|
use App\Services\Graph\GraphClientInterface;
|
|
use App\Services\Graph\GraphResponse;
|
|
use App\Services\Intune\PolicySyncService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Mockery\MockInterface;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
test('policy sync does not let enrollmentRestriction claim ESP items and reclassifies existing wrong rows', function () {
|
|
$tenant = Tenant::create([
|
|
'tenant_id' => 'tenant-sync-collision',
|
|
'name' => 'Tenant Sync Collision',
|
|
'metadata' => [],
|
|
'is_current' => true,
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
|
|
// Simulate an older bug: ESP row was synced under enrollmentRestriction.
|
|
$wrong = Policy::create([
|
|
'tenant_id' => $tenant->id,
|
|
'external_id' => 'esp-1',
|
|
'policy_type' => 'enrollmentRestriction',
|
|
'display_name' => 'ESP Misclassified',
|
|
'platform' => 'all',
|
|
]);
|
|
|
|
$this->mock(GraphClientInterface::class, function (MockInterface $mock) {
|
|
$espPayload = [
|
|
'id' => 'esp-1',
|
|
'displayName' => 'Enrollment Status Page',
|
|
'@odata.type' => '#microsoft.graph.windows10EnrollmentCompletionPageConfiguration',
|
|
'deviceEnrollmentConfigurationType' => 'windows10EnrollmentCompletionPageConfiguration',
|
|
];
|
|
|
|
$mock->shouldReceive('listPolicies')
|
|
->andReturnUsing(function (string $policyType) use ($espPayload) {
|
|
if ($policyType === 'enrollmentRestriction') {
|
|
// Shared endpoint can return ESP items if unfiltered.
|
|
return new GraphResponse(true, [$espPayload]);
|
|
}
|
|
|
|
if ($policyType === 'windowsEnrollmentStatusPage') {
|
|
return new GraphResponse(true, [$espPayload]);
|
|
}
|
|
|
|
return new GraphResponse(true, []);
|
|
});
|
|
});
|
|
|
|
$service = app(PolicySyncService::class);
|
|
|
|
$service->syncPolicies($tenant, [
|
|
[
|
|
'type' => 'enrollmentRestriction',
|
|
'platform' => 'all',
|
|
'filter' => null,
|
|
],
|
|
[
|
|
'type' => 'windowsEnrollmentStatusPage',
|
|
'platform' => 'all',
|
|
'filter' => null,
|
|
],
|
|
]);
|
|
|
|
$wrong->refresh();
|
|
|
|
expect($wrong->policy_type)->toBe('windowsEnrollmentStatusPage');
|
|
});
|
|
|
|
test('policy sync classifies ESP items without relying on Graph isof filter', function () {
|
|
$tenant = Tenant::create([
|
|
'tenant_id' => 'tenant-sync-esp-no-filter',
|
|
'name' => 'Tenant Sync ESP No Filter',
|
|
'metadata' => [],
|
|
'is_current' => true,
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
|
|
$this->mock(GraphClientInterface::class, function (MockInterface $mock) {
|
|
$payload = [
|
|
[
|
|
'id' => 'esp-1',
|
|
'displayName' => 'Enrollment Status Page',
|
|
'@odata.type' => '#microsoft.graph.windows10EnrollmentCompletionPageConfiguration',
|
|
'deviceEnrollmentConfigurationType' => 'windows10EnrollmentCompletionPageConfiguration',
|
|
],
|
|
[
|
|
'id' => 'restriction-1',
|
|
'displayName' => 'Default Enrollment Restriction',
|
|
'@odata.type' => '#microsoft.graph.deviceEnrollmentPlatformRestrictionConfiguration',
|
|
'deviceEnrollmentConfigurationType' => 'deviceEnrollmentPlatformRestrictionConfiguration',
|
|
],
|
|
[
|
|
'id' => 'other-1',
|
|
'displayName' => 'Other Enrollment Config',
|
|
'@odata.type' => '#microsoft.graph.someOtherEnrollmentConfiguration',
|
|
'deviceEnrollmentConfigurationType' => 'someOtherEnrollmentConfiguration',
|
|
],
|
|
];
|
|
|
|
$mock->shouldReceive('listPolicies')
|
|
->andReturnUsing(function (string $policyType) use ($payload) {
|
|
if (in_array($policyType, [
|
|
'enrollmentRestriction',
|
|
'windowsEnrollmentStatusPage',
|
|
'deviceEnrollmentPlatformRestrictionsConfiguration',
|
|
], true)) {
|
|
return new GraphResponse(true, $payload);
|
|
}
|
|
|
|
return new GraphResponse(true, []);
|
|
});
|
|
});
|
|
|
|
$service = app(PolicySyncService::class);
|
|
|
|
$service->syncPolicies($tenant, [
|
|
[
|
|
'type' => 'windowsEnrollmentStatusPage',
|
|
'platform' => 'all',
|
|
'filter' => null,
|
|
],
|
|
[
|
|
'type' => 'deviceEnrollmentPlatformRestrictionsConfiguration',
|
|
'platform' => 'all',
|
|
'filter' => null,
|
|
],
|
|
[
|
|
'type' => 'enrollmentRestriction',
|
|
'platform' => 'all',
|
|
'filter' => null,
|
|
],
|
|
]);
|
|
|
|
$espIds = Policy::query()
|
|
->where('tenant_id', $tenant->id)
|
|
->where('policy_type', 'windowsEnrollmentStatusPage')
|
|
->pluck('external_id')
|
|
->all();
|
|
|
|
$restrictionIds = Policy::query()
|
|
->where('tenant_id', $tenant->id)
|
|
->where('policy_type', 'enrollmentRestriction')
|
|
->orderBy('external_id')
|
|
->pluck('external_id')
|
|
->all();
|
|
|
|
$platformRestrictionIds = Policy::query()
|
|
->where('tenant_id', $tenant->id)
|
|
->where('policy_type', 'deviceEnrollmentPlatformRestrictionsConfiguration')
|
|
->orderBy('external_id')
|
|
->pluck('external_id')
|
|
->all();
|
|
|
|
expect($espIds)->toMatchArray(['esp-1']);
|
|
expect($platformRestrictionIds)->toMatchArray(['restriction-1']);
|
|
expect($restrictionIds)->toMatchArray(['other-1']);
|
|
});
|
|
|
|
test('policy sync classifies enrollment configuration subtypes separately', function () {
|
|
$tenant = Tenant::create([
|
|
'tenant_id' => 'tenant-sync-enrollment-subtypes',
|
|
'name' => 'Tenant Sync Enrollment Subtypes',
|
|
'metadata' => [],
|
|
'is_current' => true,
|
|
]);
|
|
|
|
$tenant->makeCurrent();
|
|
|
|
$this->mock(GraphClientInterface::class, function (MockInterface $mock) {
|
|
$limitPayload = [
|
|
'id' => 'limit-1',
|
|
'displayName' => 'Enrollment Limit',
|
|
'@odata.type' => '#microsoft.graph.deviceEnrollmentLimitConfiguration',
|
|
'deviceEnrollmentConfigurationType' => 'deviceEnrollmentLimitConfiguration',
|
|
'limit' => 5,
|
|
];
|
|
|
|
$platformRestrictionsPayload = [
|
|
'id' => 'platform-1',
|
|
'displayName' => 'Platform Restrictions',
|
|
'@odata.type' => '#microsoft.graph.deviceEnrollmentPlatformRestrictionsConfiguration',
|
|
'deviceEnrollmentConfigurationType' => 'deviceEnrollmentPlatformRestrictionsConfiguration',
|
|
];
|
|
|
|
$notificationPayload = [
|
|
'id' => 'notify-1',
|
|
'displayName' => 'Enrollment Notifications',
|
|
'@odata.type' => '#microsoft.graph.deviceEnrollmentNotificationConfiguration',
|
|
'deviceEnrollmentConfigurationType' => 'EnrollmentNotificationsConfiguration',
|
|
];
|
|
|
|
$unfilteredPayload = [
|
|
$limitPayload,
|
|
$platformRestrictionsPayload,
|
|
$notificationPayload,
|
|
];
|
|
|
|
$mock->shouldReceive('listPolicies')
|
|
->andReturnUsing(function (string $policyType) use ($notificationPayload, $unfilteredPayload) {
|
|
if ($policyType === 'deviceEnrollmentNotificationConfiguration') {
|
|
return new GraphResponse(true, [$notificationPayload]);
|
|
}
|
|
|
|
if (in_array($policyType, [
|
|
'enrollmentRestriction',
|
|
'deviceEnrollmentLimitConfiguration',
|
|
'deviceEnrollmentPlatformRestrictionsConfiguration',
|
|
'windowsEnrollmentStatusPage',
|
|
], true)) {
|
|
return new GraphResponse(true, $unfilteredPayload);
|
|
}
|
|
|
|
return new GraphResponse(true, []);
|
|
});
|
|
});
|
|
|
|
$service = app(PolicySyncService::class);
|
|
|
|
$service->syncPolicies($tenant, [
|
|
['type' => 'deviceEnrollmentLimitConfiguration', 'platform' => 'all', 'filter' => null],
|
|
['type' => 'deviceEnrollmentPlatformRestrictionsConfiguration', 'platform' => 'all', 'filter' => null],
|
|
['type' => 'deviceEnrollmentNotificationConfiguration', 'platform' => 'all', 'filter' => null],
|
|
['type' => 'enrollmentRestriction', 'platform' => 'all', 'filter' => null],
|
|
]);
|
|
|
|
expect(Policy::query()
|
|
->where('tenant_id', $tenant->id)
|
|
->where('policy_type', 'deviceEnrollmentLimitConfiguration')
|
|
->pluck('external_id')
|
|
->all())->toMatchArray(['limit-1']);
|
|
|
|
expect(Policy::query()
|
|
->where('tenant_id', $tenant->id)
|
|
->where('policy_type', 'deviceEnrollmentPlatformRestrictionsConfiguration')
|
|
->pluck('external_id')
|
|
->all())->toMatchArray(['platform-1']);
|
|
|
|
expect(Policy::query()
|
|
->where('tenant_id', $tenant->id)
|
|
->where('policy_type', 'deviceEnrollmentNotificationConfiguration')
|
|
->pluck('external_id')
|
|
->all())->toMatchArray(['notify-1']);
|
|
});
|