TenantAtlas/app/Services/AssignmentBackupService.php
Ahmed Darrazi 0e42164937 docs(004): Add Graph API permissions documentation
- Created docs/PERMISSIONS.md with complete permission requirements
- Added logging for 403 errors in ScopeTagResolver
- Updated README with link to permissions documentation

Issue: Scope tags show 'Unknown (ID: 0)' due to missing permission
Required: DeviceManagementRBAC.Read.All with admin consent

User must:
1. Go to Azure Portal → App Registration
2. Add DeviceManagementRBAC.Read.All permission
3. Grant admin consent
4. Wait 5-10 min for propagation
5. Clear cache: php artisan cache:clear
2025-12-22 16:03:21 +01:00

145 lines
4.8 KiB
PHP

<?php
namespace App\Services;
use App\Models\BackupItem;
use App\Models\Tenant;
use App\Services\Graph\AssignmentFetcher;
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 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
$tenantId = $tenant->external_id ?? $tenant->tenant_id;
$assignments = $this->assignmentFetcher->fetch($tenantId, $policyId);
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);
$hasOrphanedGroups = false;
if (! empty($groupIds)) {
$resolvedGroups = $this->groupResolver->resolveGroupIds($groupIds, $tenantId);
$hasOrphanedGroups = collect($resolvedGroups)->contains('orphaned', true);
}
// 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 ($odataType === '#microsoft.graph.groupAssignmentTarget' && isset($target['groupId'])) {
$groupIds[] = $target['groupId'];
}
}
return array_unique($groupIds);
}
}