Resolves assignment filter names when Graph stores filter IDs at assignment root. Tracks assignment fetch success/failure and shows clearer UI states for versions. Adds scope tag fallback display in backup set items. Restored versions now capture applied assignments consistently. Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #8
297 lines
10 KiB
PHP
297 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Intune;
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
class CompliancePolicyNormalizer implements PolicyTypeNormalizer
|
|
{
|
|
public function __construct(
|
|
private readonly DefaultPolicyNormalizer $defaultNormalizer,
|
|
) {}
|
|
|
|
public function supports(string $policyType): bool
|
|
{
|
|
return $policyType === 'deviceCompliancePolicy';
|
|
}
|
|
|
|
/**
|
|
* @return array{status: string, settings: array<int, array<string, mixed>>, settings_table?: array<string, mixed>, warnings: array<int, string>}
|
|
*/
|
|
public function normalize(?array $snapshot, string $policyType, ?string $platform = null): array
|
|
{
|
|
$snapshot = $snapshot ?? [];
|
|
$normalized = $this->defaultNormalizer->normalize($snapshot, $policyType, $platform);
|
|
|
|
if ($snapshot === []) {
|
|
return $normalized;
|
|
}
|
|
|
|
$normalized['settings'] = array_values(array_filter(
|
|
$normalized['settings'],
|
|
fn (array $block) => strtolower((string) ($block['title'] ?? '')) !== 'general'
|
|
));
|
|
|
|
foreach ($this->buildComplianceBlocks($snapshot) as $block) {
|
|
$normalized['settings'][] = $block;
|
|
}
|
|
|
|
return $normalized;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function flattenForDiff(?array $snapshot, string $policyType, ?string $platform = null): array
|
|
{
|
|
return $this->defaultNormalizer->flattenForDiff($snapshot, $policyType, $platform);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function buildComplianceBlocks(array $snapshot): array
|
|
{
|
|
$blocks = [];
|
|
$groups = $this->groupedFields();
|
|
$usedKeys = [];
|
|
|
|
foreach ($groups as $title => $group) {
|
|
$rows = $this->buildRows($snapshot, $group['keys'], $group['labels'] ?? []);
|
|
|
|
if ($rows === []) {
|
|
continue;
|
|
}
|
|
|
|
$blocks[] = [
|
|
'type' => 'table',
|
|
'title' => $title,
|
|
'rows' => $rows,
|
|
];
|
|
|
|
$usedKeys = array_merge($usedKeys, $group['keys']);
|
|
}
|
|
|
|
$additionalRows = $this->buildAdditionalRows($snapshot, $usedKeys);
|
|
|
|
if ($additionalRows !== []) {
|
|
$blocks[] = [
|
|
'type' => 'table',
|
|
'title' => 'Additional Settings',
|
|
'rows' => $additionalRows,
|
|
];
|
|
}
|
|
|
|
return $blocks;
|
|
}
|
|
|
|
/**
|
|
* @return array{keys: array<int, string>, labels?: array<string, string>}
|
|
*/
|
|
private function groupedFields(): array
|
|
{
|
|
return [
|
|
'Password & Access' => [
|
|
'keys' => [
|
|
'passwordRequired',
|
|
'passwordRequiredType',
|
|
'passwordBlockSimple',
|
|
'passwordMinimumLength',
|
|
'passwordMinimumCharacterSetCount',
|
|
'passwordExpirationDays',
|
|
'passwordMinutesOfInactivityBeforeLock',
|
|
'passwordPreviousPasswordBlockCount',
|
|
'passwordRequiredToUnlockFromIdle',
|
|
],
|
|
'labels' => [
|
|
'passwordRequired' => 'Password required',
|
|
'passwordRequiredType' => 'Password required type',
|
|
'passwordBlockSimple' => 'Block simple passwords',
|
|
'passwordMinimumLength' => 'Password minimum length',
|
|
'passwordMinimumCharacterSetCount' => 'Password minimum character set count',
|
|
'passwordExpirationDays' => 'Password expiration days',
|
|
'passwordMinutesOfInactivityBeforeLock' => 'Password idle lock (minutes)',
|
|
'passwordPreviousPasswordBlockCount' => 'Password history count',
|
|
'passwordRequiredToUnlockFromIdle' => 'Password required to unlock from idle',
|
|
],
|
|
],
|
|
'Defender & Threat Protection' => [
|
|
'keys' => [
|
|
'defenderEnabled',
|
|
'defenderVersion',
|
|
'antivirusRequired',
|
|
'antiSpywareRequired',
|
|
'rtpEnabled',
|
|
'signatureOutOfDate',
|
|
'deviceThreatProtectionEnabled',
|
|
'deviceThreatProtectionRequiredSecurityLevel',
|
|
'requireHealthyDeviceReport',
|
|
],
|
|
'labels' => [
|
|
'defenderEnabled' => 'Microsoft Defender enabled',
|
|
'defenderVersion' => 'Defender version',
|
|
'antivirusRequired' => 'Antivirus required',
|
|
'antiSpywareRequired' => 'Anti-spyware required',
|
|
'rtpEnabled' => 'Real-time protection enabled',
|
|
'signatureOutOfDate' => 'Signature out of date (days)',
|
|
'deviceThreatProtectionEnabled' => 'Device threat protection enabled',
|
|
'deviceThreatProtectionRequiredSecurityLevel' => 'Threat protection required level',
|
|
'requireHealthyDeviceReport' => 'Require healthy device report',
|
|
],
|
|
],
|
|
'Encryption & Integrity' => [
|
|
'keys' => [
|
|
'bitLockerEnabled',
|
|
'storageRequireEncryption',
|
|
'tpmRequired',
|
|
'secureBootEnabled',
|
|
'codeIntegrityEnabled',
|
|
'memoryIntegrityEnabled',
|
|
'kernelDmaProtectionEnabled',
|
|
'firmwareProtectionEnabled',
|
|
'virtualizationBasedSecurityEnabled',
|
|
'earlyLaunchAntiMalwareDriverEnabled',
|
|
],
|
|
'labels' => [
|
|
'bitLockerEnabled' => 'BitLocker required',
|
|
'storageRequireEncryption' => 'Storage encryption required',
|
|
'tpmRequired' => 'TPM required',
|
|
'secureBootEnabled' => 'Secure boot required',
|
|
'codeIntegrityEnabled' => 'Code integrity required',
|
|
'memoryIntegrityEnabled' => 'Memory integrity required',
|
|
'kernelDmaProtectionEnabled' => 'Kernel DMA protection required',
|
|
'firmwareProtectionEnabled' => 'Firmware protection required',
|
|
'virtualizationBasedSecurityEnabled' => 'Virtualization-based security required',
|
|
'earlyLaunchAntiMalwareDriverEnabled' => 'Early launch anti-malware required',
|
|
],
|
|
],
|
|
'Operating System' => [
|
|
'keys' => [
|
|
'osMinimumVersion',
|
|
'osMaximumVersion',
|
|
'mobileOsMinimumVersion',
|
|
'mobileOsMaximumVersion',
|
|
'validOperatingSystemBuildRanges',
|
|
'wslDistributions',
|
|
],
|
|
'labels' => [
|
|
'osMinimumVersion' => 'OS minimum version',
|
|
'osMaximumVersion' => 'OS maximum version',
|
|
'mobileOsMinimumVersion' => 'Mobile OS minimum version',
|
|
'mobileOsMaximumVersion' => 'Mobile OS maximum version',
|
|
'validOperatingSystemBuildRanges' => 'Valid OS build ranges',
|
|
'wslDistributions' => 'Allowed WSL distributions',
|
|
],
|
|
],
|
|
'Firewall' => [
|
|
'keys' => [
|
|
'activeFirewallRequired',
|
|
],
|
|
'labels' => [
|
|
'activeFirewallRequired' => 'Active firewall required',
|
|
],
|
|
],
|
|
'Compliance Signals' => [
|
|
'keys' => [
|
|
'configurationManagerComplianceRequired',
|
|
'deviceCompliancePolicyScript',
|
|
],
|
|
'labels' => [
|
|
'configurationManagerComplianceRequired' => 'ConfigMgr compliance required',
|
|
'deviceCompliancePolicyScript' => 'Compliance policy script',
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $labels
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function buildRows(array $snapshot, array $keys, array $labels = []): array
|
|
{
|
|
$rows = [];
|
|
|
|
foreach ($keys as $key) {
|
|
if (! array_key_exists($key, $snapshot)) {
|
|
continue;
|
|
}
|
|
|
|
$rows[] = [
|
|
'label' => $labels[$key] ?? Str::headline($key),
|
|
'value' => $this->formatValue($snapshot[$key]),
|
|
];
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $usedKeys
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function buildAdditionalRows(array $snapshot, array $usedKeys): array
|
|
{
|
|
$ignoredKeys = array_merge($this->ignoredKeys(), $usedKeys);
|
|
$rows = [];
|
|
|
|
foreach ($snapshot as $key => $value) {
|
|
if (! is_string($key)) {
|
|
continue;
|
|
}
|
|
|
|
if (in_array($key, $ignoredKeys, true)) {
|
|
continue;
|
|
}
|
|
|
|
$rows[] = [
|
|
'label' => Str::headline($key),
|
|
'value' => $this->formatValue($value),
|
|
];
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function ignoredKeys(): array
|
|
{
|
|
return [
|
|
'@odata.context',
|
|
'@odata.type',
|
|
'id',
|
|
'version',
|
|
'createdDateTime',
|
|
'lastModifiedDateTime',
|
|
'supportsScopeTags',
|
|
'roleScopeTagIds',
|
|
'assignments',
|
|
'createdBy',
|
|
'lastModifiedBy',
|
|
'omaSettings',
|
|
'settings',
|
|
'settingsDelta',
|
|
'displayName',
|
|
'description',
|
|
'name',
|
|
'platform',
|
|
'platforms',
|
|
'technologies',
|
|
'settingCount',
|
|
'settingsCount',
|
|
'templateReference',
|
|
];
|
|
}
|
|
|
|
private function formatValue(mixed $value): mixed
|
|
{
|
|
if (is_array($value)) {
|
|
return json_encode($value, JSON_PRETTY_PRINT);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
}
|