167 lines
5.5 KiB
PHP
167 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Intune;
|
|
|
|
use App\Models\Policy;
|
|
use App\Models\PolicyVersion;
|
|
use App\Models\Tenant;
|
|
use App\Services\Graph\AssignmentFetcher;
|
|
use App\Services\Graph\GroupResolver;
|
|
use App\Services\Graph\ScopeTagResolver;
|
|
use Carbon\CarbonImmutable;
|
|
|
|
class VersionService
|
|
{
|
|
public function __construct(
|
|
private readonly AuditLogger $auditLogger,
|
|
private readonly PolicySnapshotService $snapshotService,
|
|
private readonly AssignmentFetcher $assignmentFetcher,
|
|
private readonly GroupResolver $groupResolver,
|
|
private readonly ScopeTagResolver $scopeTagResolver,
|
|
) {}
|
|
|
|
public function captureVersion(
|
|
Policy $policy,
|
|
array $payload,
|
|
?string $createdBy = null,
|
|
array $metadata = [],
|
|
?array $assignments = null,
|
|
?array $scopeTags = null,
|
|
): PolicyVersion {
|
|
$versionNumber = $this->nextVersionNumber($policy);
|
|
|
|
$version = PolicyVersion::create([
|
|
'tenant_id' => $policy->tenant_id,
|
|
'policy_id' => $policy->id,
|
|
'version_number' => $versionNumber,
|
|
'policy_type' => $policy->policy_type,
|
|
'platform' => $policy->platform,
|
|
'created_by' => $createdBy,
|
|
'captured_at' => CarbonImmutable::now(),
|
|
'snapshot' => $payload,
|
|
'metadata' => $metadata,
|
|
'assignments' => $assignments,
|
|
'scope_tags' => $scopeTags,
|
|
'assignments_hash' => $assignments ? hash('sha256', json_encode($assignments)) : null,
|
|
'scope_tags_hash' => $scopeTags ? hash('sha256', json_encode($scopeTags)) : null,
|
|
]);
|
|
|
|
$this->auditLogger->log(
|
|
tenant: $policy->tenant,
|
|
action: 'policy.versioned',
|
|
context: [
|
|
'metadata' => [
|
|
'policy_id' => $policy->id,
|
|
'version_number' => $versionNumber,
|
|
],
|
|
],
|
|
actorEmail: $createdBy,
|
|
resourceType: 'policy',
|
|
resourceId: (string) $policy->id
|
|
);
|
|
|
|
return $version;
|
|
}
|
|
|
|
public function captureFromGraph(
|
|
Tenant $tenant,
|
|
Policy $policy,
|
|
?string $createdBy = null,
|
|
array $metadata = [],
|
|
bool $includeAssignments = true,
|
|
bool $includeScopeTags = true,
|
|
): PolicyVersion {
|
|
$graphOptions = $tenant->graphOptions();
|
|
$tenantIdentifier = $graphOptions['tenant'] ?? $tenant->graphTenantId() ?? (string) $tenant->getKey();
|
|
|
|
$snapshot = $this->snapshotService->fetch($tenant, $policy, $createdBy);
|
|
|
|
if (isset($snapshot['failure'])) {
|
|
$reason = $snapshot['failure']['reason'] ?? 'Unable to fetch policy snapshot';
|
|
|
|
throw new \RuntimeException($reason);
|
|
}
|
|
|
|
$payload = $snapshot['payload'];
|
|
$assignments = null;
|
|
$scopeTags = null;
|
|
$assignmentMetadata = [];
|
|
|
|
if ($includeAssignments) {
|
|
try {
|
|
$rawAssignments = $this->assignmentFetcher->fetch($tenantIdentifier, $policy->external_id, $graphOptions);
|
|
|
|
if (! empty($rawAssignments)) {
|
|
$assignments = $rawAssignments;
|
|
|
|
// Resolve groups
|
|
$groupIds = collect($rawAssignments)
|
|
->pluck('target.groupId')
|
|
->filter()
|
|
->unique()
|
|
->values()
|
|
->toArray();
|
|
|
|
$resolvedGroups = $this->groupResolver->resolveGroupIds($groupIds, $tenantIdentifier, $graphOptions);
|
|
|
|
$assignmentMetadata['has_orphaned_assignments'] = collect($resolvedGroups)
|
|
->contains(fn (array $group) => $group['orphaned'] ?? false);
|
|
$assignmentMetadata['assignments_count'] = count($rawAssignments);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$assignmentMetadata['assignments_fetch_failed'] = true;
|
|
$assignmentMetadata['assignments_fetch_error'] = $e->getMessage();
|
|
}
|
|
}
|
|
|
|
if ($includeScopeTags) {
|
|
$scopeTagIds = $payload['roleScopeTagIds'] ?? ['0'];
|
|
$scopeTags = $this->resolveScopeTags($tenant, $scopeTagIds);
|
|
}
|
|
|
|
$metadata = array_merge(
|
|
['source' => 'version_capture'],
|
|
$metadata,
|
|
$assignmentMetadata
|
|
);
|
|
|
|
return $this->captureVersion(
|
|
policy: $policy,
|
|
payload: $payload,
|
|
createdBy: $createdBy,
|
|
metadata: $metadata,
|
|
assignments: $assignments,
|
|
scopeTags: $scopeTags,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $scopeTagIds
|
|
* @return array{ids:array<int, string>,names:array<int, string>}
|
|
*/
|
|
private function resolveScopeTags(Tenant $tenant, array $scopeTagIds): array
|
|
{
|
|
$scopeTags = $this->scopeTagResolver->resolve($scopeTagIds, $tenant);
|
|
|
|
$names = [];
|
|
foreach ($scopeTagIds as $id) {
|
|
$scopeTag = collect($scopeTags)->firstWhere('id', $id);
|
|
$names[] = $scopeTag['displayName'] ?? ($id === '0' ? 'Default' : "Unknown (ID: {$id})");
|
|
}
|
|
|
|
return [
|
|
'ids' => $scopeTagIds,
|
|
'names' => $names,
|
|
];
|
|
}
|
|
|
|
private function nextVersionNumber(Policy $policy): int
|
|
{
|
|
$current = PolicyVersion::query()
|
|
->where('policy_id', $policy->id)
|
|
->max('version_number');
|
|
|
|
return (int) ($current ?? 0) + 1;
|
|
}
|
|
}
|