046-inventory-sync-button #47
@ -235,13 +235,9 @@ private function isEndpointSecurityConfigurationPolicy(array $policyData): bool
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($templateReference as $value) {
|
||||
if (is_string($value) && stripos($value, 'endpoint') !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$templateFamily = $templateReference['templateFamily'] ?? null;
|
||||
|
||||
return false;
|
||||
return is_string($templateFamily) && str_starts_with(strtolower(trim($templateFamily)), 'endpointsecurity');
|
||||
}
|
||||
|
||||
private function isSecurityBaselineConfigurationPolicy(array $policyData): bool
|
||||
@ -253,17 +249,8 @@ private function isSecurityBaselineConfigurationPolicy(array $policyData): bool
|
||||
}
|
||||
|
||||
$templateFamily = $templateReference['templateFamily'] ?? null;
|
||||
if (is_string($templateFamily) && stripos($templateFamily, 'baseline') !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($templateReference as $value) {
|
||||
if (is_string($value) && stripos($value, 'baseline') !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return is_string($templateFamily) && strcasecmp(trim($templateFamily), 'securityBaseline') === 0;
|
||||
}
|
||||
|
||||
private function isEnrollmentStatusPageItem(array $policyData): bool
|
||||
|
||||
@ -140,6 +140,10 @@ private function executeRun(InventorySyncRun $run, Tenant $tenant, array $normal
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->shouldSkipPolicyForSelectedType($policyType, $policyData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$externalId = $policyData['id'] ?? $policyData['external_id'] ?? null;
|
||||
if (! is_string($externalId) || $externalId === '') {
|
||||
continue;
|
||||
@ -215,6 +219,55 @@ private function executeRun(InventorySyncRun $run, Tenant $tenant, array $normal
|
||||
}
|
||||
}
|
||||
|
||||
private function shouldSkipPolicyForSelectedType(string $selectedPolicyType, array $policyData): bool
|
||||
{
|
||||
$configurationPolicyTypes = ['settingsCatalogPolicy', 'endpointSecurityPolicy', 'securityBaselinePolicy'];
|
||||
|
||||
if (! in_array($selectedPolicyType, $configurationPolicyTypes, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->resolveConfigurationPolicyType($policyData) !== $selectedPolicyType;
|
||||
}
|
||||
|
||||
private function resolveConfigurationPolicyType(array $policyData): string
|
||||
{
|
||||
$templateReference = $policyData['templateReference'] ?? null;
|
||||
$templateFamily = null;
|
||||
if (is_array($templateReference)) {
|
||||
$templateFamily = $templateReference['templateFamily'] ?? null;
|
||||
}
|
||||
|
||||
if (is_string($templateFamily) && strcasecmp(trim($templateFamily), 'securityBaseline') === 0) {
|
||||
return 'securityBaselinePolicy';
|
||||
}
|
||||
|
||||
if ($this->isEndpointSecurityConfigurationPolicy($policyData, $templateFamily)) {
|
||||
return 'endpointSecurityPolicy';
|
||||
}
|
||||
|
||||
return 'settingsCatalogPolicy';
|
||||
}
|
||||
|
||||
private function isEndpointSecurityConfigurationPolicy(array $policyData, ?string $templateFamily): bool
|
||||
{
|
||||
$technologies = $policyData['technologies'] ?? null;
|
||||
|
||||
if (is_string($technologies) && strcasecmp(trim($technologies), 'endpointSecurity') === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_array($technologies)) {
|
||||
foreach ($technologies as $technology) {
|
||||
if (is_string($technology) && strcasecmp(trim($technology), 'endpointSecurity') === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is_string($templateFamily) && str_starts_with(strtolower(trim($templateFamily)), 'endpointsecurity');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
|
||||
15
specs/045-settingscatalog-classification/plan.md
Normal file
15
specs/045-settingscatalog-classification/plan.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Plan: Settings Catalog Classification
|
||||
|
||||
## Approach
|
||||
- Trace how Inventory derives **Type** and **Category** for policies.
|
||||
- Fix policy-type resolution/canonicalization so Settings Catalog cannot be classified as Security Baselines.
|
||||
- Add a regression test at the classification layer.
|
||||
|
||||
## Expected Touch Points
|
||||
- `app/Services/Intune/PolicyClassificationService.php`
|
||||
- Possibly Inventory mapping/display logic if category is derived elsewhere
|
||||
- `tests/` regression coverage
|
||||
|
||||
## Rollout
|
||||
- Code change affects future sync runs.
|
||||
- To correct existing rows, rerun the Inventory Sync for the tenant.
|
||||
18
specs/045-settingscatalog-classification/spec.md
Normal file
18
specs/045-settingscatalog-classification/spec.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Spec: Settings Catalog Classification
|
||||
|
||||
## Problem
|
||||
Some Settings Catalog policies show up as **Type: Security Baselines** and **Category: Endpoint Security** in Inventory UI.
|
||||
|
||||
## Goal
|
||||
Ensure Settings Catalog policies are consistently classified as:
|
||||
- **Type**: Settings Catalog Policy
|
||||
- **Category**: Configuration
|
||||
|
||||
## Non-Goals
|
||||
- Changing UI layout or adding new filters
|
||||
- Bulk cleanup of historical data beyond what a normal re-sync updates
|
||||
|
||||
## Acceptance Criteria
|
||||
- Classification logic never maps Settings Catalog policies to Security Baselines
|
||||
- A regression test covers the misclassification scenario
|
||||
- After the next Inventory Sync, affected items are stored with the correct `policy_type`/category
|
||||
7
specs/045-settingscatalog-classification/tasks.md
Normal file
7
specs/045-settingscatalog-classification/tasks.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Tasks: Settings Catalog Classification
|
||||
|
||||
- [x] T001 Run Spec Kit prerequisites + gather context
|
||||
- [x] T002 Locate where Inventory Type/Category is derived
|
||||
- [x] T003 Fix Settings Catalog misclassification in policy classification
|
||||
- [x] T004 Add regression test
|
||||
- [x] T005 Run targeted tests + Pint (dirty)
|
||||
@ -101,6 +101,61 @@ public function request(string $method, string $path, array $options = []): Grap
|
||||
expect($items->first()->last_seen_run_id)->toBe($runB->id);
|
||||
});
|
||||
|
||||
test('configuration policy inventory filtering: settings catalog is not stored as security baseline', function () {
|
||||
$tenant = Tenant::factory()->create();
|
||||
|
||||
$settingsCatalogLookalike = [
|
||||
'id' => 'pol-1',
|
||||
'name' => 'Windows 11 SettingsCatalog-Test',
|
||||
'@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy',
|
||||
'technologies' => ['mdm'],
|
||||
'templateReference' => [
|
||||
'templateDisplayName' => 'Windows Security Baseline (name only)',
|
||||
],
|
||||
];
|
||||
|
||||
$securityBaseline = [
|
||||
'id' => 'pol-2',
|
||||
'name' => 'Baseline Policy',
|
||||
'@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy',
|
||||
'templateReference' => [
|
||||
'templateFamily' => 'securityBaseline',
|
||||
],
|
||||
];
|
||||
|
||||
app()->instance(GraphClientInterface::class, fakeGraphClient([
|
||||
'settingsCatalogPolicy' => [$settingsCatalogLookalike, $securityBaseline],
|
||||
'securityBaselinePolicy' => [$settingsCatalogLookalike, $securityBaseline],
|
||||
]));
|
||||
|
||||
$selection = [
|
||||
'policy_types' => ['settingsCatalogPolicy', 'securityBaselinePolicy'],
|
||||
'categories' => ['Configuration', 'Endpoint Security'],
|
||||
'include_foundations' => false,
|
||||
'include_dependencies' => false,
|
||||
];
|
||||
|
||||
app(InventorySyncService::class)->syncNow($tenant, $selection);
|
||||
|
||||
expect(\App\Models\InventoryItem::query()
|
||||
->where('tenant_id', $tenant->id)
|
||||
->where('policy_type', 'securityBaselinePolicy')
|
||||
->where('external_id', 'pol-1')
|
||||
->exists())->toBeFalse();
|
||||
|
||||
expect(\App\Models\InventoryItem::query()
|
||||
->where('tenant_id', $tenant->id)
|
||||
->where('policy_type', 'settingsCatalogPolicy')
|
||||
->where('external_id', 'pol-1')
|
||||
->exists())->toBeTrue();
|
||||
|
||||
expect(\App\Models\InventoryItem::query()
|
||||
->where('tenant_id', $tenant->id)
|
||||
->where('policy_type', 'securityBaselinePolicy')
|
||||
->where('external_id', 'pol-2')
|
||||
->exists())->toBeTrue();
|
||||
});
|
||||
|
||||
test('meta whitelist drops unknown keys without failing', function () {
|
||||
$tenant = Tenant::factory()->create();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user