389 lines
13 KiB
PHP
389 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Intune;
|
|
|
|
use App\Models\Tenant;
|
|
use App\Services\Graph\GraphClientInterface;
|
|
use Illuminate\Support\Arr;
|
|
|
|
class ConfigurationPolicyTemplateResolver
|
|
{
|
|
/**
|
|
* @var array<string, array<string, array{success:bool,template:?array,reason:?string}>>
|
|
*/
|
|
private array $templateCache = [];
|
|
|
|
/**
|
|
* @var array<string, array<string, array{success:bool,templates:array<int,array>,reason:?string}>>
|
|
*/
|
|
private array $familyCache = [];
|
|
|
|
/**
|
|
* @var array<string, array<string, array{success:bool,definition_ids:array<int,string>,reason:?string}>>
|
|
*/
|
|
private array $templateDefinitionCache = [];
|
|
|
|
public function __construct(
|
|
private readonly GraphClientInterface $graphClient,
|
|
) {}
|
|
|
|
/**
|
|
* @param array<string, mixed> $templateReference
|
|
* @param array<string, mixed> $graphOptions
|
|
* @return array{success:bool,template_id:?string,template_reference:?array,reason:?string,warnings:array<int,string>}
|
|
*/
|
|
public function resolveTemplateReference(Tenant $tenant, array $templateReference, array $graphOptions = []): array
|
|
{
|
|
$warnings = [];
|
|
|
|
$templateId = $this->extractString($templateReference, ['templateId', 'TemplateId']);
|
|
$templateFamily = $this->extractString($templateReference, ['templateFamily', 'TemplateFamily']);
|
|
$templateDisplayName = $this->extractString($templateReference, ['templateDisplayName', 'TemplateDisplayName']);
|
|
$templateDisplayVersion = $this->extractString($templateReference, ['templateDisplayVersion', 'TemplateDisplayVersion']);
|
|
|
|
if ($templateId !== null) {
|
|
$templateOutcome = $this->getTemplate($tenant, $templateId, $graphOptions);
|
|
|
|
if ($templateOutcome['success']) {
|
|
return [
|
|
'success' => true,
|
|
'template_id' => $templateId,
|
|
'template_reference' => $templateReference,
|
|
'reason' => null,
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
|
|
if ($templateFamily === null) {
|
|
return [
|
|
'success' => false,
|
|
'template_id' => null,
|
|
'template_reference' => null,
|
|
'reason' => $templateOutcome['reason'] ?? "Template '{$templateId}' is not available in the tenant.",
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
}
|
|
|
|
if ($templateFamily === null) {
|
|
return [
|
|
'success' => false,
|
|
'template_id' => null,
|
|
'template_reference' => null,
|
|
'reason' => 'Template reference is missing templateFamily and cannot be resolved.',
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
|
|
$listOutcome = $this->listTemplatesByFamily($tenant, $templateFamily, $graphOptions);
|
|
|
|
if (! $listOutcome['success']) {
|
|
return [
|
|
'success' => false,
|
|
'template_id' => null,
|
|
'template_reference' => null,
|
|
'reason' => $listOutcome['reason'] ?? "Unable to list templates for family '{$templateFamily}'.",
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
|
|
$candidates = $this->chooseTemplateCandidate(
|
|
templates: $listOutcome['templates'],
|
|
templateDisplayName: $templateDisplayName,
|
|
templateDisplayVersion: $templateDisplayVersion,
|
|
);
|
|
|
|
if (count($candidates) !== 1) {
|
|
$reason = count($candidates) === 0
|
|
? "No templates found for family '{$templateFamily}'."
|
|
: "Multiple templates found for family '{$templateFamily}' (cannot resolve automatically).";
|
|
|
|
return [
|
|
'success' => false,
|
|
'template_id' => null,
|
|
'template_reference' => null,
|
|
'reason' => $reason,
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
|
|
$candidate = $candidates[0];
|
|
$resolvedId = is_array($candidate) ? ($candidate['id'] ?? null) : null;
|
|
|
|
if (! is_string($resolvedId) || $resolvedId === '') {
|
|
return [
|
|
'success' => false,
|
|
'template_id' => null,
|
|
'template_reference' => null,
|
|
'reason' => "Template candidate for family '{$templateFamily}' is missing an id.",
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
|
|
if ($templateId !== null && $templateId !== $resolvedId) {
|
|
$warnings[] = sprintf("TemplateId '%s' not found; mapped to '%s' via templateFamily.", $templateId, $resolvedId);
|
|
}
|
|
|
|
$templateReference['templateId'] = $resolvedId;
|
|
|
|
if (! isset($templateReference['templateDisplayName']) && isset($candidate['displayName'])) {
|
|
$templateReference['templateDisplayName'] = $candidate['displayName'];
|
|
}
|
|
|
|
if (! isset($templateReference['templateDisplayVersion']) && isset($candidate['displayVersion'])) {
|
|
$templateReference['templateDisplayVersion'] = $candidate['displayVersion'];
|
|
}
|
|
|
|
return [
|
|
'success' => true,
|
|
'template_id' => $resolvedId,
|
|
'template_reference' => $templateReference,
|
|
'reason' => null,
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $graphOptions
|
|
* @return array{success:bool,template:?array,reason:?string}
|
|
*/
|
|
public function getTemplate(Tenant $tenant, string $templateId, array $graphOptions = []): array
|
|
{
|
|
$tenantKey = $this->tenantKey($tenant, $graphOptions);
|
|
|
|
if (isset($this->templateCache[$tenantKey][$templateId])) {
|
|
return $this->templateCache[$tenantKey][$templateId];
|
|
}
|
|
|
|
$context = array_merge($tenant->graphOptions(), Arr::except($graphOptions, ['platform']));
|
|
$path = sprintf('/deviceManagement/configurationPolicyTemplates/%s', urlencode($templateId));
|
|
$response = $this->graphClient->request('GET', $path, $context);
|
|
|
|
if ($response->failed()) {
|
|
return $this->templateCache[$tenantKey][$templateId] = [
|
|
'success' => false,
|
|
'template' => null,
|
|
'reason' => $response->meta['error_message'] ?? 'Template lookup failed.',
|
|
];
|
|
}
|
|
|
|
return $this->templateCache[$tenantKey][$templateId] = [
|
|
'success' => true,
|
|
'template' => $response->data,
|
|
'reason' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $graphOptions
|
|
* @return array{success:bool,templates:array<int,array>,reason:?string}
|
|
*/
|
|
public function listTemplatesByFamily(Tenant $tenant, string $templateFamily, array $graphOptions = []): array
|
|
{
|
|
$tenantKey = $this->tenantKey($tenant, $graphOptions);
|
|
$cacheKey = strtolower($templateFamily);
|
|
|
|
if (isset($this->familyCache[$tenantKey][$cacheKey])) {
|
|
return $this->familyCache[$tenantKey][$cacheKey];
|
|
}
|
|
|
|
$escapedFamily = str_replace("'", "''", $templateFamily);
|
|
|
|
$context = array_merge($tenant->graphOptions(), Arr::except($graphOptions, ['platform']), [
|
|
'query' => [
|
|
'$filter' => "templateFamily eq '{$escapedFamily}'",
|
|
'$top' => 999,
|
|
],
|
|
]);
|
|
|
|
$response = $this->graphClient->request('GET', '/deviceManagement/configurationPolicyTemplates', $context);
|
|
|
|
if ($response->failed()) {
|
|
return $this->familyCache[$tenantKey][$cacheKey] = [
|
|
'success' => false,
|
|
'templates' => [],
|
|
'reason' => $response->meta['error_message'] ?? 'Template list failed.',
|
|
];
|
|
}
|
|
|
|
$value = $response->data['value'] ?? [];
|
|
$templates = is_array($value) ? array_values(array_filter($value, static fn ($item) => is_array($item))) : [];
|
|
|
|
return $this->familyCache[$tenantKey][$cacheKey] = [
|
|
'success' => true,
|
|
'templates' => $templates,
|
|
'reason' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $graphOptions
|
|
* @return array{success:bool,definition_ids:array<int,string>,reason:?string}
|
|
*/
|
|
public function fetchTemplateSettingDefinitionIds(Tenant $tenant, string $templateId, array $graphOptions = []): array
|
|
{
|
|
$tenantKey = $this->tenantKey($tenant, $graphOptions);
|
|
|
|
if (isset($this->templateDefinitionCache[$tenantKey][$templateId])) {
|
|
return $this->templateDefinitionCache[$tenantKey][$templateId];
|
|
}
|
|
|
|
$context = array_merge($tenant->graphOptions(), Arr::except($graphOptions, ['platform']), [
|
|
'query' => [
|
|
'$expand' => 'settingDefinitions',
|
|
'$top' => 999,
|
|
],
|
|
]);
|
|
|
|
$path = sprintf('/deviceManagement/configurationPolicyTemplates/%s/settingTemplates', urlencode($templateId));
|
|
$response = $this->graphClient->request('GET', $path, $context);
|
|
|
|
if ($response->failed()) {
|
|
return $this->templateDefinitionCache[$tenantKey][$templateId] = [
|
|
'success' => false,
|
|
'definition_ids' => [],
|
|
'reason' => $response->meta['error_message'] ?? 'Template definitions lookup failed.',
|
|
];
|
|
}
|
|
|
|
$value = $response->data['value'] ?? [];
|
|
$templates = is_array($value) ? $value : [];
|
|
$definitionIds = [];
|
|
|
|
foreach ($templates as $settingTemplate) {
|
|
if (! is_array($settingTemplate)) {
|
|
continue;
|
|
}
|
|
|
|
$definitions = $settingTemplate['settingDefinitions'] ?? null;
|
|
|
|
if (! is_array($definitions)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($definitions as $definition) {
|
|
if (! is_array($definition)) {
|
|
continue;
|
|
}
|
|
|
|
$id = $definition['id'] ?? null;
|
|
|
|
if (is_string($id) && $id !== '') {
|
|
$definitionIds[] = $id;
|
|
}
|
|
}
|
|
}
|
|
|
|
$definitionIds = array_values(array_unique($definitionIds));
|
|
|
|
return $this->templateDefinitionCache[$tenantKey][$templateId] = [
|
|
'success' => true,
|
|
'definition_ids' => $definitionIds,
|
|
'reason' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<int, mixed> $settings
|
|
* @return array<int, string>
|
|
*/
|
|
public function extractSettingDefinitionIds(array $settings): array
|
|
{
|
|
$ids = [];
|
|
|
|
$walk = function (mixed $node) use (&$walk, &$ids): void {
|
|
if (! is_array($node)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($node as $key => $value) {
|
|
if (is_string($key) && strtolower($key) === 'settingdefinitionid' && is_string($value) && $value !== '') {
|
|
$ids[] = $value;
|
|
}
|
|
|
|
$walk($value);
|
|
}
|
|
};
|
|
|
|
$walk($settings);
|
|
|
|
return array_values(array_unique($ids));
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array> $templates
|
|
* @return array<int, array>
|
|
*/
|
|
private function chooseTemplateCandidate(array $templates, ?string $templateDisplayName, ?string $templateDisplayVersion): array
|
|
{
|
|
$candidates = $templates;
|
|
|
|
$active = array_values(array_filter($candidates, static function (array $template): bool {
|
|
$state = $template['lifecycleState'] ?? null;
|
|
|
|
return is_string($state) && strtolower($state) === 'active';
|
|
}));
|
|
|
|
if ($active !== []) {
|
|
$candidates = $active;
|
|
}
|
|
|
|
if ($templateDisplayVersion !== null) {
|
|
$byVersion = array_values(array_filter($candidates, static function (array $template) use ($templateDisplayVersion): bool {
|
|
$version = $template['displayVersion'] ?? null;
|
|
|
|
return is_string($version) && $version === $templateDisplayVersion;
|
|
}));
|
|
|
|
if ($byVersion !== []) {
|
|
$candidates = $byVersion;
|
|
}
|
|
}
|
|
|
|
if ($templateDisplayName !== null) {
|
|
$byName = array_values(array_filter($candidates, static function (array $template) use ($templateDisplayName): bool {
|
|
$name = $template['displayName'] ?? null;
|
|
|
|
return is_string($name) && $name === $templateDisplayName;
|
|
}));
|
|
|
|
if ($byName !== []) {
|
|
$candidates = $byName;
|
|
}
|
|
}
|
|
|
|
return $candidates;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
* @param array<int, string> $keys
|
|
*/
|
|
private function extractString(array $payload, array $keys): ?string
|
|
{
|
|
$normalized = array_map('strtolower', $keys);
|
|
|
|
foreach ($payload as $key => $value) {
|
|
if (! is_string($key) || ! in_array(strtolower($key), $normalized, true)) {
|
|
continue;
|
|
}
|
|
|
|
if (is_string($value) && trim($value) !== '') {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $graphOptions
|
|
*/
|
|
private function tenantKey(Tenant $tenant, array $graphOptions): string
|
|
{
|
|
$tenantId = $graphOptions['tenant'] ?? $tenant->graphTenantId() ?? (string) $tenant->getKey();
|
|
|
|
return (string) $tenantId;
|
|
}
|
|
}
|