Replaced legacy tenant and environment bindings in the BaselineDriftEngine with the new ProviderResourceIdentity framework as defined in Spec 382. This ensures cross-environment compatibility and deterministic baseline matching. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #453
109 lines
3.5 KiB
PHP
109 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Baselines;
|
|
|
|
use App\Support\Resources\ResourceIdentity;
|
|
|
|
final class BaselineSubjectKey
|
|
{
|
|
public static function workspaceSafeSubjectExternalId(string $policyType, string $subjectKey): string
|
|
{
|
|
return hash('sha256', $policyType.'|'.$subjectKey);
|
|
}
|
|
|
|
public static function forProviderResourceIdentity(
|
|
string $subjectDomain,
|
|
SubjectClass|string $subjectClass,
|
|
string $subjectTypeKey,
|
|
ResourceIdentity $identity,
|
|
): ?string {
|
|
$domain = self::canonicalSegment($subjectDomain);
|
|
$class = self::canonicalSegment($subjectClass instanceof SubjectClass ? $subjectClass->value : $subjectClass);
|
|
$type = self::canonicalSegment($subjectTypeKey);
|
|
$provider = self::canonicalSegment($identity->providerKey);
|
|
$resourceType = self::canonicalSegment($identity->providerResourceType ?? 'none');
|
|
$stableIdentity = $identity->stableIdentityValue();
|
|
|
|
if ($domain === null || $class === null || $type === null || $provider === null || $resourceType === null || $stableIdentity === null) {
|
|
return null;
|
|
}
|
|
|
|
return implode(':', [
|
|
'provider-resource',
|
|
'v1',
|
|
$domain,
|
|
$class,
|
|
$type,
|
|
$provider,
|
|
$resourceType,
|
|
$identity->identityKind,
|
|
hash('sha256', $stableIdentity),
|
|
]);
|
|
}
|
|
|
|
public static function fromProviderResourceIdentity(
|
|
string $subjectDomain,
|
|
SubjectClass|string $subjectClass,
|
|
string $subjectTypeKey,
|
|
string $providerKey,
|
|
?string $providerResourceType,
|
|
string $stableIdentity,
|
|
string $identityKind = 'provider_resource',
|
|
): ?string {
|
|
return self::forProviderResourceIdentity(
|
|
$subjectDomain,
|
|
$subjectClass,
|
|
$subjectTypeKey,
|
|
new ResourceIdentity(
|
|
providerKey: $providerKey,
|
|
identityKind: $identityKind,
|
|
providerResourceType: $providerResourceType,
|
|
providerResourceId: $identityKind === 'provider_resource' ? $stableIdentity : null,
|
|
canonicalDiscriminator: $identityKind === 'provider_resource' ? null : $stableIdentity,
|
|
),
|
|
);
|
|
}
|
|
|
|
public static function isProviderResourceCanonicalKey(?string $subjectKey): bool
|
|
{
|
|
if (! is_string($subjectKey) || trim($subjectKey) === '') {
|
|
return false;
|
|
}
|
|
|
|
$parts = explode(':', trim($subjectKey));
|
|
|
|
if (count($parts) !== 9) {
|
|
return false;
|
|
}
|
|
|
|
[$prefix, $version, $domain, $class, $type, $provider, $resourceType, $identityKind, $hash] = $parts;
|
|
|
|
if ($prefix !== 'provider-resource' || $version !== 'v1') {
|
|
return false;
|
|
}
|
|
|
|
foreach ([$domain, $class, $type, $provider, $resourceType, $identityKind] as $segment) {
|
|
if (! is_string($segment) || ! preg_match('/^[a-z0-9._-]+$/', $segment)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return preg_match('/^[a-f0-9]{64}$/', $hash) === 1;
|
|
}
|
|
|
|
private static function canonicalSegment(?string $value): ?string
|
|
{
|
|
if (! is_string($value)) {
|
|
return null;
|
|
}
|
|
|
|
$normalized = trim(mb_strtolower($value));
|
|
$normalized = preg_replace('/[^a-z0-9._-]+/', '-', $normalized);
|
|
$normalized = is_string($normalized) ? trim($normalized, '-') : null;
|
|
|
|
return $normalized !== null && $normalized !== '' ? $normalized : null;
|
|
}
|
|
}
|