Added `ProviderResourceBinding` model, migrations, policies, and supporting framework for canonical resource identity mapping as defined in Spec 381. This provides the structural capability to resolve baseline and posture discrepancies by binding logical entities across source providers to canonical identities. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #452
141 lines
4.7 KiB
PHP
141 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Baselines;
|
|
|
|
use App\Support\Inventory\InventoryPolicyTypeMeta;
|
|
use App\Support\Resources\ResourceIdentity;
|
|
|
|
final class BaselineSubjectKey
|
|
{
|
|
public static function forPolicy(string $policyType, ?string $displayName = null, ?string $subjectExternalId = null): ?string
|
|
{
|
|
return match (InventoryPolicyTypeMeta::baselineCompareIdentityStrategy($policyType)) {
|
|
'external_id' => self::fromExternalId($policyType, $subjectExternalId),
|
|
default => self::fromDisplayName($displayName),
|
|
};
|
|
}
|
|
|
|
public static function fromDisplayName(?string $displayName): ?string
|
|
{
|
|
if (! is_string($displayName)) {
|
|
return null;
|
|
}
|
|
|
|
$trimmed = trim($displayName);
|
|
|
|
if ($trimmed === '') {
|
|
return null;
|
|
}
|
|
|
|
$collapsed = preg_replace('/\\s+/u', ' ', $trimmed);
|
|
$collapsed = is_string($collapsed) ? $collapsed : $trimmed;
|
|
|
|
$normalized = mb_strtolower($collapsed);
|
|
$normalized = trim($normalized);
|
|
|
|
return $normalized !== '' ? $normalized : null;
|
|
}
|
|
|
|
public static function fromExternalId(string $policyType, ?string $subjectExternalId): ?string
|
|
{
|
|
if (! is_string($subjectExternalId)) {
|
|
return null;
|
|
}
|
|
|
|
$normalizedId = trim(mb_strtolower($subjectExternalId));
|
|
|
|
if ($normalizedId === '') {
|
|
return null;
|
|
}
|
|
|
|
return hash('sha256', trim(mb_strtolower($policyType)).'|'.$normalizedId);
|
|
}
|
|
|
|
public static function workspaceSafeSubjectExternalId(string $policyType, string $subjectKey): string
|
|
{
|
|
return hash('sha256', $policyType.'|'.$subjectKey);
|
|
}
|
|
|
|
public static function workspaceSafeSubjectExternalIdForPolicy(string $policyType, ?string $displayName = null, ?string $subjectExternalId = null): ?string
|
|
{
|
|
$identityInput = match (InventoryPolicyTypeMeta::baselineCompareIdentityStrategy($policyType)) {
|
|
'external_id' => is_string($subjectExternalId) ? trim(mb_strtolower($subjectExternalId)) : null,
|
|
default => self::fromDisplayName($displayName),
|
|
};
|
|
|
|
if (! is_string($identityInput) || $identityInput === '') {
|
|
return null;
|
|
}
|
|
|
|
return self::workspaceSafeSubjectExternalId($policyType, $identityInput);
|
|
}
|
|
|
|
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,
|
|
),
|
|
);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|