TenantAtlas/app/Services/Intune/SecretClassificationService.php
2026-03-07 17:41:55 +01:00

118 lines
3.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Intune;
use Illuminate\Support\Str;
final class SecretClassificationService
{
public const string REDACTED = '[REDACTED]';
public const int REDACTION_VERSION = 1;
/**
* @var array<int, string>
*/
private const array PROTECTED_FIELD_NAMES = [
'access_token',
'apikey',
'api_key',
'authorization',
'bearer',
'bearer_token',
'client_secret',
'cookie',
'password',
'presharedkey',
'pre_shared_key',
'private_key',
'refresh_token',
'sas_token',
'secret',
'set-cookie',
'shared_secret',
'token',
];
/**
* @var array<string, array<int, string>>
*/
private const array PROTECTED_JSON_POINTERS = [
'snapshot' => [
'/wifi/password',
'/authentication/clientSecret',
],
'assignments' => [],
'scope_tags' => [],
'audit' => [],
'verification' => [],
'ops_failure' => [],
];
public function protectsField(string $sourceBucket, string $fieldName, ?string $jsonPointer = null): bool
{
$fieldName = $this->normalizeFieldName($fieldName);
if ($fieldName === '') {
return false;
}
$protectedPointers = self::PROTECTED_JSON_POINTERS[$sourceBucket] ?? [];
if (is_string($jsonPointer) && in_array($jsonPointer, $protectedPointers, true)) {
return true;
}
return in_array($fieldName, self::PROTECTED_FIELD_NAMES, true);
}
public function sanitizeAuditString(string $value): string
{
return $this->sanitizeMessageLikeString($value, '[REDACTED]');
}
public function sanitizeOpsFailureString(string $value): string
{
$sanitized = $this->sanitizeMessageLikeString($value, '[REDACTED_SECRET]');
$sanitized = preg_replace('/[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}/i', '[REDACTED_EMAIL]', $sanitized) ?? $sanitized;
return $sanitized;
}
private function sanitizeMessageLikeString(string $value, string $replacement): string
{
$patterns = [
'/\bAuthorization\s*:\s*Bearer\s+[A-Za-z0-9\-\._~\+\/]+=*/i',
'/\bBearer\s+[A-Za-z0-9\-\._~\+\/]+=*/i',
'/\b[A-Za-z0-9\-_]{20,}\.[A-Za-z0-9\-_]{20,}\.[A-Za-z0-9\-_]{20,}\b/',
];
foreach ($patterns as $pattern) {
$value = preg_replace($pattern, $replacement, $value) ?? $value;
}
foreach (self::PROTECTED_FIELD_NAMES as $fieldName) {
$quotedPattern = sprintf('/"%s"\s*:\s*"[^"]*"/i', preg_quote($fieldName, '/'));
$pairPattern = sprintf('/\b%s\b\s*[:=]\s*[^\s,;]+/i', preg_quote($fieldName, '/'));
$value = preg_replace($quotedPattern, sprintf('"%s":"%s"', $fieldName, $replacement), $value) ?? $value;
$value = preg_replace($pairPattern, sprintf('%s=%s', $fieldName, $replacement), $value) ?? $value;
}
return $value;
}
private function normalizeFieldName(string $fieldName): string
{
$fieldName = Str::of($fieldName)
->replace(['-', ' '], '_')
->snake()
->lower()
->toString();
return trim($fieldName);
}
}