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.
221 lines
11 KiB
PHP
221 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\AuditLog;
|
|
use App\Models\InventoryItem;
|
|
use App\Models\OperationRun;
|
|
use App\Models\PolicyVersion;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\ProviderResourceBinding;
|
|
use App\Services\Resources\ProviderResourceBindingService;
|
|
use App\Support\Audit\AuditActionId;
|
|
use App\Support\Baselines\BaselineSubjectKey;
|
|
use App\Support\Baselines\SubjectClass;
|
|
use App\Support\Resources\ProviderResourceBindingStatus;
|
|
use App\Support\Resources\ProviderResourceResolutionMode;
|
|
use App\Support\Resources\ResourceIdentity;
|
|
|
|
function providerResourceBindingAttributes(array $overrides = []): array
|
|
{
|
|
return array_replace([
|
|
'subject_domain' => 'baseline',
|
|
'subject_class' => SubjectClass::PolicyBacked,
|
|
'subject_type_key' => 'deviceConfiguration',
|
|
'operator_note' => 'Operator confirmed this provider resource binding after reviewing duplicate labels.',
|
|
], $overrides);
|
|
}
|
|
|
|
it('creates manual fake-provider bindings with scoped references and safe audit metadata', function (): void {
|
|
[$actor, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$connection = ProviderConnection::factory()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'provider' => 'fake-provider',
|
|
]);
|
|
$run = OperationRun::factory()->forTenant($tenant)->create();
|
|
$inventory = InventoryItem::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
$policyVersion = PolicyVersion::factory()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
]);
|
|
[, $snapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
$binding = app(ProviderResourceBindingService::class)->createManualBinding(
|
|
actor: $actor,
|
|
environment: $tenant,
|
|
identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'fake-policy-1'),
|
|
attributes: providerResourceBindingAttributes([
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'source_operation_run_id' => (int) $run->getKey(),
|
|
'source_inventory_item_id' => (int) $inventory->getKey(),
|
|
'source_policy_version_id' => (int) $policyVersion->getKey(),
|
|
'source_baseline_snapshot_id' => (int) $snapshot->getKey(),
|
|
'display_label' => 'Duplicate policy label',
|
|
'resolution_reason' => 'duplicate-provider-resource',
|
|
]),
|
|
);
|
|
|
|
expect((int) $binding->workspace_id)->toBe((int) $tenant->workspace_id)
|
|
->and((int) $binding->managed_environment_id)->toBe((int) $tenant->getKey())
|
|
->and($binding->provider_key)->toBe('fake-provider')
|
|
->and($binding->resolution_mode)->toBe(ProviderResourceResolutionMode::ManualBinding)
|
|
->and($binding->binding_status)->toBe(ProviderResourceBindingStatus::Active)
|
|
->and((int) $binding->provider_connection_id)->toBe((int) $connection->getKey())
|
|
->and((int) $binding->source_operation_run_id)->toBe((int) $run->getKey())
|
|
->and((int) $binding->source_inventory_item_id)->toBe((int) $inventory->getKey())
|
|
->and((int) $binding->source_policy_version_id)->toBe((int) $policyVersion->getKey())
|
|
->and((int) $binding->source_baseline_snapshot_id)->toBe((int) $snapshot->getKey());
|
|
|
|
$audit = AuditLog::query()
|
|
->where('action', AuditActionId::ProviderResourceBindingCreated->value)
|
|
->where('resource_type', 'provider_resource_binding')
|
|
->firstOrFail();
|
|
|
|
expect((int) $audit->workspace_id)->toBe((int) $tenant->workspace_id)
|
|
->and((int) $audit->managed_environment_id)->toBe((int) $tenant->getKey())
|
|
->and(data_get($audit->metadata, 'provider_key'))->toBe('fake-provider')
|
|
->and(data_get($audit->metadata, 'resolution_mode'))->toBe(ProviderResourceResolutionMode::ManualBinding->value)
|
|
->and(data_get($audit->metadata, 'source_operation_run_id'))->toBe((int) $run->getKey())
|
|
->and(data_get($audit->metadata, 'operator_note'))->toBeNull()
|
|
->and(data_get($audit->metadata, 'operator_note_sha256'))->toBe(hash('sha256', providerResourceBindingAttributes()['operator_note']))
|
|
->and(data_get($audit->metadata, 'operator_note_length'))->toBe(mb_strlen(providerResourceBindingAttributes()['operator_note']));
|
|
});
|
|
|
|
it('supersedes an existing active binding and keeps exactly one active row', function (): void {
|
|
[$actor, $tenant] = createUserWithTenant(role: 'owner');
|
|
$service = app(ProviderResourceBindingService::class);
|
|
$identity = ResourceIdentity::providerResource('fake-provider', 'policy', 'same-subject');
|
|
|
|
$first = $service->createExactProviderIdentity($actor, $tenant, $identity, providerResourceBindingAttributes([
|
|
'operator_note' => 'Initial identity binding.',
|
|
]));
|
|
|
|
$second = $service->createManualBinding($actor, $tenant, $identity, providerResourceBindingAttributes([
|
|
'operator_note' => 'Replacement identity binding after manual review.',
|
|
]));
|
|
|
|
expect($first->refresh()->binding_status)->toBe(ProviderResourceBindingStatus::Superseded)
|
|
->and($second->binding_status)->toBe(ProviderResourceBindingStatus::Active)
|
|
->and(ProviderResourceBinding::query()
|
|
->where('workspace_id', (int) $tenant->workspace_id)
|
|
->where('managed_environment_id', (int) $tenant->getKey())
|
|
->where('provider_key', 'fake-provider')
|
|
->where('canonical_subject_key', $second->canonical_subject_key)
|
|
->active()
|
|
->count())->toBe(1);
|
|
|
|
$audit = AuditLog::query()
|
|
->where('action', AuditActionId::ProviderResourceBindingSuperseded->value)
|
|
->firstOrFail();
|
|
|
|
expect(data_get($audit->metadata, 'old_binding_id'))->toBe((int) $first->getKey())
|
|
->and(data_get($audit->metadata, 'new_binding_id'))->toBe((int) $second->getKey())
|
|
->and(data_get($audit->metadata, 'resolution_mode'))->toBe(ProviderResourceResolutionMode::ManualBinding->value);
|
|
});
|
|
|
|
it('revokes active bindings with audit metadata', function (): void {
|
|
[$actor, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$binding = app(ProviderResourceBindingService::class)->createAcceptedLimitation(
|
|
actor: $actor,
|
|
environment: $tenant,
|
|
identity: ResourceIdentity::unsupported('fake-provider', 'foundation-resource', 'unsupported-subject'),
|
|
attributes: providerResourceBindingAttributes([
|
|
'operator_note' => 'Accepted limitation for unsupported coverage.',
|
|
'subject_class' => SubjectClass::FoundationBacked,
|
|
'subject_type_key' => 'foundationResource',
|
|
]),
|
|
);
|
|
|
|
$revoked = app(ProviderResourceBindingService::class)->revoke(
|
|
actor: $actor,
|
|
binding: $binding,
|
|
operatorNote: 'Revoked because the limitation is no longer accepted.',
|
|
);
|
|
|
|
expect($revoked->binding_status)->toBe(ProviderResourceBindingStatus::Revoked)
|
|
->and($revoked->ended_at)->not->toBeNull();
|
|
|
|
$audit = AuditLog::query()
|
|
->where('action', AuditActionId::ProviderResourceBindingRevoked->value)
|
|
->firstOrFail();
|
|
|
|
expect(data_get($audit->metadata, 'old_binding_id'))->toBe((int) $binding->getKey())
|
|
->and(data_get($audit->metadata, 'binding_status'))->toBe(ProviderResourceBindingStatus::Revoked->value)
|
|
->and(data_get($audit->metadata, 'operator_note'))->toBeNull();
|
|
});
|
|
|
|
it('requires operator notes for binding decisions', function (): void {
|
|
[$actor, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
expect(fn () => app(ProviderResourceBindingService::class)->createManualBinding(
|
|
actor: $actor,
|
|
environment: $tenant,
|
|
identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'missing-note'),
|
|
attributes: providerResourceBindingAttributes(['operator_note' => '']),
|
|
))->toThrow(InvalidArgumentException::class, 'operator note');
|
|
});
|
|
|
|
it('rejects arbitrary canonical subject key overrides for provider resource decisions', function (): void {
|
|
[$actor, $tenant] = createUserWithTenant(role: 'owner');
|
|
$identity = ResourceIdentity::providerResource('fake-provider', 'policy', 'canonical-validation');
|
|
$expectedCanonicalKey = BaselineSubjectKey::forProviderResourceIdentity(
|
|
subjectDomain: 'baseline',
|
|
subjectClass: SubjectClass::PolicyBacked,
|
|
subjectTypeKey: 'deviceConfiguration',
|
|
identity: $identity,
|
|
);
|
|
|
|
expect(fn () => app(ProviderResourceBindingService::class)->createManualBinding(
|
|
actor: $actor,
|
|
environment: $tenant,
|
|
identity: $identity,
|
|
attributes: providerResourceBindingAttributes([
|
|
'canonical_subject_key' => 'duplicate policy',
|
|
]),
|
|
))->toThrow(InvalidArgumentException::class, 'canonical subject keys');
|
|
|
|
$binding = app(ProviderResourceBindingService::class)->createManualBinding(
|
|
actor: $actor,
|
|
environment: $tenant,
|
|
identity: $identity,
|
|
attributes: providerResourceBindingAttributes([
|
|
'canonical_subject_key' => $expectedCanonicalKey,
|
|
]),
|
|
);
|
|
|
|
expect($binding->canonical_subject_key)->toBe($expectedCanonicalKey);
|
|
});
|
|
|
|
it('persists every v1 resolution mode without Microsoft literals', function (string $method, ResourceIdentity $identity, ProviderResourceResolutionMode $mode): void {
|
|
[$actor, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$binding = app(ProviderResourceBindingService::class)->{$method}(
|
|
actor: $actor,
|
|
environment: $tenant,
|
|
identity: $identity,
|
|
attributes: providerResourceBindingAttributes([
|
|
'subject_class' => SubjectClass::FoundationBacked,
|
|
'subject_type_key' => 'fakeSubjectType',
|
|
'operator_note' => 'Provider-neutral decision for '.$mode->value,
|
|
]),
|
|
);
|
|
|
|
expect($binding->provider_key)->toBe('fake-provider')
|
|
->and($binding->resolution_mode)->toBe($mode)
|
|
->and($binding->canonical_subject_key)->toContain('fake-provider')
|
|
->and($binding->canonical_subject_key)->not->toContain('microsoft');
|
|
})->with([
|
|
'exact provider identity' => ['createExactProviderIdentity', ResourceIdentity::providerResource('fake-provider', 'policy', 'exact-1'), ProviderResourceResolutionMode::ExactProviderIdentity],
|
|
'provider default as canonical builtin' => ['createCanonicalBuiltin', ResourceIdentity::canonicalDefault('fake-provider', 'default', 'provider-default'), ProviderResourceResolutionMode::CanonicalBuiltin],
|
|
'canonical virtual target' => ['createCanonicalVirtualTarget', ResourceIdentity::virtualTarget('fake-provider', 'target', 'virtual-target'), ProviderResourceResolutionMode::CanonicalVirtualTarget],
|
|
'manual binding' => ['createManualBinding', ResourceIdentity::providerResource('fake-provider', 'policy', 'manual-1'), ProviderResourceResolutionMode::ManualBinding],
|
|
'excluded non governed' => ['createExclusion', ResourceIdentity::unsupported('fake-provider', 'object', 'excluded-1'), ProviderResourceResolutionMode::ExcludedNonGoverned],
|
|
'accepted limitation' => ['createAcceptedLimitation', ResourceIdentity::unsupported('fake-provider', 'object', 'limited-1'), ProviderResourceResolutionMode::AcceptedLimitation],
|
|
'unsupported coverage' => ['markUnsupported', ResourceIdentity::unsupported('fake-provider', 'object', 'unsupported-1'), ProviderResourceResolutionMode::UnsupportedCoverage],
|
|
'missing expected' => ['markMissingExpected', ResourceIdentity::unknown('fake-provider', 'object', 'missing-1'), ProviderResourceResolutionMode::MissingExpected],
|
|
]);
|