180 lines
6.2 KiB
PHP
180 lines
6.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Intune;
|
|
|
|
use App\Models\Policy;
|
|
use App\Models\Tenant;
|
|
use App\Services\Graph\GraphClientInterface;
|
|
use App\Services\Graph\GraphErrorMapper;
|
|
use App\Services\Graph\GraphLogger;
|
|
use Illuminate\Support\Arr;
|
|
use RuntimeException;
|
|
use Throwable;
|
|
|
|
class PolicySyncService
|
|
{
|
|
public function __construct(
|
|
private readonly GraphClientInterface $graphClient,
|
|
private readonly GraphLogger $graphLogger,
|
|
) {}
|
|
|
|
/**
|
|
* Sync supported policies for a tenant from Microsoft Graph.
|
|
*
|
|
* @return array<int> IDs of policies synced or created
|
|
*/
|
|
public function syncPolicies(Tenant $tenant, ?array $supportedTypes = null): array
|
|
{
|
|
if (! $tenant->isActive()) {
|
|
throw new \RuntimeException('Tenant is archived or inactive.');
|
|
}
|
|
|
|
$types = $supportedTypes ?? config('tenantpilot.supported_policy_types', []);
|
|
$synced = [];
|
|
$tenantIdentifier = $tenant->tenant_id ?? $tenant->external_id;
|
|
|
|
foreach ($types as $typeConfig) {
|
|
$policyType = $typeConfig['type'];
|
|
$platform = $typeConfig['platform'] ?? null;
|
|
$filter = $typeConfig['filter'] ?? null;
|
|
|
|
$this->graphLogger->logRequest('list_policies', [
|
|
'tenant' => $tenantIdentifier,
|
|
'policy_type' => $policyType,
|
|
'platform' => $platform,
|
|
'filter' => $filter,
|
|
]);
|
|
|
|
try {
|
|
$response = $this->graphClient->listPolicies($policyType, [
|
|
'tenant' => $tenantIdentifier,
|
|
'client_id' => $tenant->app_client_id,
|
|
'client_secret' => $tenant->app_client_secret,
|
|
'platform' => $platform,
|
|
'filter' => $filter,
|
|
]);
|
|
} catch (Throwable $throwable) {
|
|
throw GraphErrorMapper::fromThrowable($throwable, [
|
|
'policy_type' => $policyType,
|
|
'tenant_id' => $tenant->id,
|
|
'tenant_identifier' => $tenantIdentifier,
|
|
]);
|
|
}
|
|
|
|
$this->graphLogger->logResponse('list_policies', $response, [
|
|
'policy_type' => $policyType,
|
|
'tenant_id' => $tenant->id,
|
|
'tenant' => $tenantIdentifier,
|
|
]);
|
|
|
|
if ($response->failed()) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($response->data as $policyData) {
|
|
$externalId = $policyData['id'] ?? $policyData['external_id'] ?? null;
|
|
|
|
if ($externalId === null) {
|
|
continue;
|
|
}
|
|
|
|
$displayName = $policyData['displayName'] ?? $policyData['name'] ?? 'Unnamed policy';
|
|
$policyPlatform = $platform ?? ($policyData['platform'] ?? null);
|
|
|
|
$existingWithDifferentType = Policy::query()
|
|
->where('tenant_id', $tenant->id)
|
|
->where('external_id', $externalId)
|
|
->where('policy_type', '!=', $policyType)
|
|
->exists();
|
|
|
|
if ($existingWithDifferentType) {
|
|
continue;
|
|
}
|
|
|
|
$policy = Policy::updateOrCreate(
|
|
[
|
|
'tenant_id' => $tenant->id,
|
|
'external_id' => $externalId,
|
|
'policy_type' => $policyType,
|
|
],
|
|
[
|
|
'display_name' => $displayName,
|
|
'platform' => $policyPlatform,
|
|
'last_synced_at' => now(),
|
|
'ignored_at' => null,
|
|
'metadata' => Arr::except($policyData, ['id', 'external_id', 'displayName', 'name', 'platform']),
|
|
]
|
|
);
|
|
|
|
$synced[] = $policy->id;
|
|
}
|
|
}
|
|
|
|
return $synced;
|
|
}
|
|
|
|
/**
|
|
* Re-fetch a single policy from Graph and update local metadata.
|
|
*/
|
|
public function syncPolicy(Tenant $tenant, Policy $policy): void
|
|
{
|
|
if (! $tenant->isActive()) {
|
|
throw new RuntimeException('Tenant is archived or inactive.');
|
|
}
|
|
|
|
$tenantIdentifier = $tenant->tenant_id ?? $tenant->external_id;
|
|
|
|
$this->graphLogger->logRequest('get_policy', [
|
|
'tenant' => $tenantIdentifier,
|
|
'policy_type' => $policy->policy_type,
|
|
'policy_id' => $policy->external_id,
|
|
'platform' => $policy->platform,
|
|
]);
|
|
|
|
try {
|
|
$response = $this->graphClient->getPolicy($policy->policy_type, $policy->external_id, [
|
|
'tenant' => $tenantIdentifier,
|
|
'client_id' => $tenant->app_client_id,
|
|
'client_secret' => $tenant->app_client_secret,
|
|
'platform' => $policy->platform,
|
|
]);
|
|
} catch (Throwable $throwable) {
|
|
throw GraphErrorMapper::fromThrowable($throwable, [
|
|
'policy_type' => $policy->policy_type,
|
|
'policy_id' => $policy->external_id,
|
|
'tenant_id' => $tenant->id,
|
|
'tenant_identifier' => $tenantIdentifier,
|
|
]);
|
|
}
|
|
|
|
$this->graphLogger->logResponse('get_policy', $response, [
|
|
'tenant_id' => $tenant->id,
|
|
'tenant' => $tenantIdentifier,
|
|
'policy_type' => $policy->policy_type,
|
|
'policy_id' => $policy->external_id,
|
|
]);
|
|
|
|
if ($response->failed()) {
|
|
$message = $response->errors[0]['message'] ?? $response->data['error']['message'] ?? 'Graph request failed.';
|
|
|
|
throw new RuntimeException($message);
|
|
}
|
|
|
|
$payload = $response->data['payload'] ?? $response->data;
|
|
|
|
if (! is_array($payload)) {
|
|
throw new RuntimeException('Invalid Graph response payload.');
|
|
}
|
|
|
|
$displayName = $payload['displayName'] ?? $payload['name'] ?? $policy->display_name;
|
|
$platform = $payload['platform'] ?? $policy->platform;
|
|
|
|
$policy->forceFill([
|
|
'display_name' => $displayName,
|
|
'platform' => $platform,
|
|
'last_synced_at' => now(),
|
|
'metadata' => Arr::except($payload, ['id', 'external_id', 'displayName', 'name', 'platform']),
|
|
])->save();
|
|
}
|
|
}
|