Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m12s
Replaced legacy tenant and environment bindings in the BaselineDriftEngine with the new ProviderResourceIdentity framework as defined in Spec 382.
172 lines
4.9 KiB
PHP
172 lines
4.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Baselines\Evidence;
|
|
|
|
use App\Models\Policy;
|
|
use App\Models\PolicyVersion;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Support\Baselines\BaselineSubjectKey;
|
|
use App\Support\Baselines\SubjectClass;
|
|
use App\Support\Inventory\InventoryPolicyTypeMeta;
|
|
use App\Support\Resources\ResourceIdentity;
|
|
use Carbon\CarbonImmutable;
|
|
use InvalidArgumentException;
|
|
use Throwable;
|
|
|
|
final class BaselinePolicyVersionResolver
|
|
{
|
|
/**
|
|
* Cached map of (managed_environment_id, policy_type) => subject_key => policy_id.
|
|
*
|
|
* @var array<int, array<string, array<string, int>>>
|
|
*/
|
|
private array $policyIdIndex = [];
|
|
|
|
public function resolve(
|
|
ManagedEnvironment $tenant,
|
|
string $policyType,
|
|
string $subjectKey,
|
|
?string $observedAt,
|
|
): ?int {
|
|
$tenantId = (int) $tenant->getKey();
|
|
|
|
$policyType = trim($policyType);
|
|
$subjectKey = trim($subjectKey);
|
|
|
|
if ($tenantId <= 0 || $policyType === '' || $subjectKey === '') {
|
|
return null;
|
|
}
|
|
|
|
$observedAtCarbon = $this->parseObservedAt($observedAt);
|
|
|
|
if (! $observedAtCarbon instanceof CarbonImmutable) {
|
|
return null;
|
|
}
|
|
|
|
$policyId = $this->resolvePolicyId($tenantId, $policyType, $subjectKey);
|
|
|
|
if ($policyId === null) {
|
|
return null;
|
|
}
|
|
|
|
$rangeStart = $observedAtCarbon;
|
|
$rangeEnd = $observedAtCarbon->addSecond();
|
|
|
|
$versionId = PolicyVersion::query()
|
|
->where('managed_environment_id', $tenantId)
|
|
->where('policy_id', $policyId)
|
|
->whereNull('deleted_at')
|
|
->where('captured_at', '>=', $rangeStart)
|
|
->where('captured_at', '<', $rangeEnd)
|
|
->orderByDesc('captured_at')
|
|
->orderByDesc('version_number')
|
|
->orderByDesc('id')
|
|
->value('id');
|
|
|
|
return is_numeric($versionId) ? (int) $versionId : null;
|
|
}
|
|
|
|
private function parseObservedAt(?string $observedAt): ?CarbonImmutable
|
|
{
|
|
if (! is_string($observedAt)) {
|
|
return null;
|
|
}
|
|
|
|
$observedAt = trim($observedAt);
|
|
|
|
if ($observedAt === '') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return CarbonImmutable::parse($observedAt);
|
|
} catch (Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function resolvePolicyId(int $tenantId, string $policyType, string $subjectKey): ?int
|
|
{
|
|
if (! array_key_exists($tenantId, $this->policyIdIndex) || ! array_key_exists($policyType, $this->policyIdIndex[$tenantId])) {
|
|
$this->policyIdIndex[$tenantId][$policyType] = $this->buildIndex($tenantId, $policyType);
|
|
}
|
|
|
|
$policyId = $this->policyIdIndex[$tenantId][$policyType][$subjectKey] ?? null;
|
|
|
|
return is_numeric($policyId) ? (int) $policyId : null;
|
|
}
|
|
|
|
/**
|
|
* Build a subject_key => policy_id map for a given tenant + policy_type.
|
|
*
|
|
* If multiple policies map to the same subject_key, that key is treated as ambiguous and excluded.
|
|
*
|
|
* @return array<string, int>
|
|
*/
|
|
private function buildIndex(int $tenantId, string $policyType): array
|
|
{
|
|
$policies = Policy::query()
|
|
->where('managed_environment_id', $tenantId)
|
|
->where('policy_type', $policyType)
|
|
->get(['id', 'display_name', 'external_id']);
|
|
|
|
/** @var array<string, int> $index */
|
|
$index = [];
|
|
|
|
/** @var array<string, true> $ambiguous */
|
|
$ambiguous = [];
|
|
|
|
foreach ($policies as $policy) {
|
|
if (! $policy instanceof Policy) {
|
|
continue;
|
|
}
|
|
|
|
$key = $this->canonicalSubjectKeyForPolicy($policyType, $policy);
|
|
|
|
if ($key === null) {
|
|
continue;
|
|
}
|
|
|
|
if (array_key_exists($key, $index)) {
|
|
$ambiguous[$key] = true;
|
|
|
|
continue;
|
|
}
|
|
|
|
$index[$key] = (int) $policy->getKey();
|
|
}
|
|
|
|
foreach (array_keys($ambiguous) as $key) {
|
|
unset($index[$key]);
|
|
}
|
|
|
|
return $index;
|
|
}
|
|
|
|
private function canonicalSubjectKeyForPolicy(string $policyType, Policy $policy): ?string
|
|
{
|
|
$externalId = is_string($policy->external_id) ? trim($policy->external_id) : '';
|
|
|
|
if ($externalId === '') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$identity = ResourceIdentity::providerResource('inventory', $policyType, $externalId);
|
|
} catch (InvalidArgumentException) {
|
|
return null;
|
|
}
|
|
|
|
return BaselineSubjectKey::forProviderResourceIdentity(
|
|
subjectDomain: 'baseline',
|
|
subjectClass: InventoryPolicyTypeMeta::isFoundation($policyType)
|
|
? SubjectClass::FoundationBacked
|
|
: SubjectClass::PolicyBacked,
|
|
subjectTypeKey: $policyType,
|
|
identity: $identity,
|
|
);
|
|
}
|
|
}
|