TenantAtlas/app/Support/OpsUx/AssignmentJobFingerprint.php

140 lines
4.9 KiB
PHP

<?php
namespace App\Support\OpsUx;
final class AssignmentJobFingerprint
{
public static function forFetch(
int $backupItemId,
string $tenantExternalId,
string $policyExternalId,
): string {
return self::hash('assignments.fetch', [
'backup_item_id' => $backupItemId,
'tenant_external_id' => trim($tenantExternalId),
'policy_external_id' => trim($policyExternalId),
]);
}
/**
* @param array<int, array<string, mixed>> $assignments
* @param array<string, mixed> $groupMapping
* @param array<string, mixed> $foundationMapping
*/
public static function forRestore(
int $restoreRunId,
int $tenantId,
string $policyType,
string $policyId,
array $assignments,
array $groupMapping,
array $foundationMapping = [],
): string {
return self::hash('assignments.restore', [
'restore_run_id' => $restoreRunId,
'tenant_id' => $tenantId,
'policy_type' => trim($policyType),
'policy_id' => trim($policyId),
'assignments' => self::normalizeAssignments($assignments),
'group_mapping' => $groupMapping,
'foundation_mapping' => $foundationMapping,
]);
}
public static function executionIdentityKey(
string $jobType,
int $tenantId,
string $fingerprint,
?int $operationRunId = null,
): string {
if (is_int($operationRunId) && $operationRunId > 0) {
return 'operation_run:'.$operationRunId;
}
return 'tenant:'.$tenantId
.'|job_type:'.trim($jobType)
.'|fingerprint:'.trim($fingerprint);
}
/**
* @param array<string, mixed> $identityInputs
*/
private static function hash(string $jobType, array $identityInputs): string
{
$payload = [
'job_type' => trim($jobType),
'identity' => self::normalize($identityInputs),
];
$json = json_encode($payload, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return hash('sha256', (string) $json);
}
/**
* @param array<int, array<string, mixed>> $assignments
* @return array<int, array<string, mixed>>
*/
private static function normalizeAssignments(array $assignments): array
{
$normalized = [];
foreach ($assignments as $assignment) {
if (! is_array($assignment)) {
continue;
}
$target = is_array($assignment['target'] ?? null) ? $assignment['target'] : [];
$normalized[] = [
'id' => is_scalar($assignment['id'] ?? null) ? (string) $assignment['id'] : '',
'target_type' => is_scalar($target['@odata.type'] ?? null) ? (string) $target['@odata.type'] : '',
'group_id' => is_scalar($target['groupId'] ?? null) ? (string) $target['groupId'] : '',
'assignment_filter_id' => is_scalar($assignment['deviceAndAppManagementAssignmentFilterId'] ?? null)
? (string) $assignment['deviceAndAppManagementAssignmentFilterId']
: (is_scalar($target['deviceAndAppManagementAssignmentFilterId'] ?? null) ? (string) $target['deviceAndAppManagementAssignmentFilterId'] : ''),
'assignment_filter_type' => is_scalar($assignment['deviceAndAppManagementAssignmentFilterType'] ?? null)
? (string) $assignment['deviceAndAppManagementAssignmentFilterType']
: (is_scalar($target['deviceAndAppManagementAssignmentFilterType'] ?? null) ? (string) $target['deviceAndAppManagementAssignmentFilterType'] : ''),
];
}
usort($normalized, static function (array $left, array $right): int {
$leftJson = json_encode($left, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$rightJson = json_encode($right, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return strcmp((string) $leftJson, (string) $rightJson);
});
return $normalized;
}
private static function normalize(mixed $value): mixed
{
if (! is_array($value)) {
return $value;
}
if (array_is_list($value)) {
$items = array_map(static fn (mixed $item): mixed => self::normalize($item), $value);
usort($items, static function (mixed $left, mixed $right): int {
$leftJson = json_encode($left, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$rightJson = json_encode($right, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return strcmp((string) $leftJson, (string) $rightJson);
});
return array_values($items);
}
ksort($value);
foreach ($value as $key => $item) {
$value[$key] = self::normalize($item);
}
return $value;
}
}