195 lines
6.8 KiB
PHP
195 lines
6.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\BackupItem;
|
|
use App\Models\Tenant;
|
|
use App\Services\Graph\AssignmentFetcher;
|
|
use App\Services\Graph\AssignmentFilterResolver;
|
|
use App\Services\Graph\GroupResolver;
|
|
use App\Services\Graph\ScopeTagResolver;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class AssignmentBackupService
|
|
{
|
|
public function __construct(
|
|
private readonly AssignmentFetcher $assignmentFetcher,
|
|
private readonly GroupResolver $groupResolver,
|
|
private readonly AssignmentFilterResolver $assignmentFilterResolver,
|
|
private readonly ScopeTagResolver $scopeTagResolver,
|
|
) {}
|
|
|
|
/**
|
|
* Enrich a backup item with assignments and scope tag metadata.
|
|
*
|
|
* @param BackupItem $backupItem The backup item to enrich
|
|
* @param Tenant $tenant Tenant model with credentials
|
|
* @param string $policyId Policy ID (external_id from Graph)
|
|
* @param array $policyPayload Full policy payload from Graph
|
|
* @param bool $includeAssignments Whether to fetch and include assignments
|
|
* @return BackupItem Updated backup item with assignments and metadata
|
|
*/
|
|
public function enrichWithAssignments(
|
|
BackupItem $backupItem,
|
|
Tenant $tenant,
|
|
string $policyId,
|
|
array $policyPayload,
|
|
bool $includeAssignments = false
|
|
): BackupItem {
|
|
// Extract scope tags from payload (always available in policy)
|
|
$scopeTagIds = $policyPayload['roleScopeTagIds'] ?? ['0'];
|
|
$scopeTagNames = $this->resolveScopeTagNames($scopeTagIds, $tenant);
|
|
|
|
$metadata = $backupItem->metadata ?? [];
|
|
$metadata['scope_tag_ids'] = $scopeTagIds;
|
|
$metadata['scope_tag_names'] = $scopeTagNames;
|
|
|
|
// Only fetch assignments if explicitly requested
|
|
if (! $includeAssignments) {
|
|
$metadata['assignment_count'] = 0;
|
|
$backupItem->update([
|
|
'assignments' => null,
|
|
'metadata' => $metadata,
|
|
]);
|
|
|
|
return $backupItem->refresh();
|
|
}
|
|
|
|
// Fetch assignments from Graph API
|
|
$graphOptions = $tenant->graphOptions();
|
|
$tenantId = $graphOptions['tenant'] ?? $tenant->external_id ?? $tenant->tenant_id;
|
|
$assignments = $this->assignmentFetcher->fetch($tenantId, $policyId, $graphOptions);
|
|
|
|
if (empty($assignments)) {
|
|
// No assignments or fetch failed
|
|
$metadata['assignment_count'] = 0;
|
|
$metadata['assignments_fetch_failed'] = true;
|
|
$metadata['has_orphaned_assignments'] = false;
|
|
|
|
$backupItem->update([
|
|
'assignments' => [], // Return empty array instead of null
|
|
'metadata' => $metadata,
|
|
]);
|
|
|
|
Log::warning('No assignments fetched for policy', [
|
|
'tenant_id' => $tenantId,
|
|
'policy_id' => $policyId,
|
|
'backup_item_id' => $backupItem->id,
|
|
]);
|
|
|
|
return $backupItem->refresh();
|
|
}
|
|
|
|
// Extract group IDs and resolve for orphan detection
|
|
$groupIds = $this->extractGroupIds($assignments);
|
|
$resolvedGroups = [];
|
|
$hasOrphanedGroups = false;
|
|
|
|
if (! empty($groupIds)) {
|
|
$resolvedGroups = $this->groupResolver->resolveGroupIds($groupIds, $tenantId, $graphOptions);
|
|
$hasOrphanedGroups = collect($resolvedGroups)
|
|
->contains(fn (array $group) => $group['orphaned'] ?? false);
|
|
}
|
|
|
|
$filterIds = collect($assignments)
|
|
->pluck('target.deviceAndAppManagementAssignmentFilterId')
|
|
->filter()
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
|
|
$filters = $this->assignmentFilterResolver->resolve($filterIds, $tenant);
|
|
$filterNames = collect($filters)
|
|
->pluck('displayName', 'id')
|
|
->all();
|
|
|
|
$assignments = $this->enrichAssignments($assignments, $resolvedGroups, $filterNames);
|
|
|
|
// Update backup item with assignments and metadata
|
|
$metadata['assignment_count'] = count($assignments);
|
|
$metadata['assignments_fetch_failed'] = false;
|
|
$metadata['has_orphaned_assignments'] = $hasOrphanedGroups;
|
|
|
|
$backupItem->update([
|
|
'assignments' => $assignments,
|
|
'metadata' => $metadata,
|
|
]);
|
|
|
|
Log::info('Assignments enriched for backup item', [
|
|
'tenant_id' => $tenantId,
|
|
'policy_id' => $policyId,
|
|
'backup_item_id' => $backupItem->id,
|
|
'assignment_count' => count($assignments),
|
|
'has_orphaned' => $hasOrphanedGroups,
|
|
]);
|
|
|
|
return $backupItem->refresh();
|
|
}
|
|
|
|
/**
|
|
* Resolve scope tag IDs to display names.
|
|
*/
|
|
private function resolveScopeTagNames(array $scopeTagIds, Tenant $tenant): array
|
|
{
|
|
$scopeTags = $this->scopeTagResolver->resolve($scopeTagIds, $tenant);
|
|
|
|
$names = [];
|
|
foreach ($scopeTagIds as $id) {
|
|
$scopeTag = collect($scopeTags)->firstWhere('id', $id);
|
|
$names[] = $scopeTag['displayName'] ?? "Unknown (ID: {$id})";
|
|
}
|
|
|
|
return $names;
|
|
}
|
|
|
|
/**
|
|
* Extract group IDs from assignment array.
|
|
*/
|
|
private function extractGroupIds(array $assignments): array
|
|
{
|
|
$groupIds = [];
|
|
|
|
foreach ($assignments as $assignment) {
|
|
$target = $assignment['target'] ?? [];
|
|
$odataType = $target['@odata.type'] ?? '';
|
|
|
|
if (in_array($odataType, [
|
|
'#microsoft.graph.groupAssignmentTarget',
|
|
'#microsoft.graph.exclusionGroupAssignmentTarget',
|
|
], true) && isset($target['groupId'])) {
|
|
$groupIds[] = $target['groupId'];
|
|
}
|
|
}
|
|
|
|
return array_unique($groupIds);
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $assignments
|
|
* @param array<string, array{id:string,displayName:?string,orphaned:bool}> $groups
|
|
* @param array<string, string> $filterNames
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function enrichAssignments(array $assignments, array $groups, array $filterNames): array
|
|
{
|
|
return array_map(function (array $assignment) use ($groups, $filterNames): array {
|
|
$target = $assignment['target'] ?? [];
|
|
$groupId = $target['groupId'] ?? null;
|
|
|
|
if ($groupId && isset($groups[$groupId])) {
|
|
$target['group_display_name'] = $groups[$groupId]['displayName'] ?? null;
|
|
$target['group_orphaned'] = $groups[$groupId]['orphaned'] ?? false;
|
|
}
|
|
|
|
$filterId = $target['deviceAndAppManagementAssignmentFilterId'] ?? null;
|
|
if ($filterId && isset($filterNames[$filterId])) {
|
|
$target['assignment_filter_name'] = $filterNames[$filterId];
|
|
}
|
|
|
|
$assignment['target'] = $target;
|
|
|
|
return $assignment;
|
|
}, $assignments);
|
|
}
|
|
}
|