TenantAtlas/apps/platform/tests/Feature/TenantConfiguration/Spec425EntraCertifiedComparePackTest.php
Ahmed Darrazi 39d0353e03
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m22s
feat: complete spec 425 enta certified compare pack
2026-07-02 00:55:04 +02:00

212 lines
11 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\ProviderConnection;
use App\Models\TenantConfigurationResourceEvidence;
use App\Services\TenantConfiguration\EntraCertifiedComparePackEvaluator;
use App\Services\TenantConfiguration\EntraCertifiedComparePackResult;
use App\Support\TenantConfiguration\CaptureOutcome;
use App\Support\TenantConfiguration\CoverageLevel;
use App\Support\TenantConfiguration\EvidenceState;
use App\Support\TenantConfiguration\IdentityState;
use Tests\Support\TenantConfiguration\Spec425Fixtures as Spec425;
it('Spec425 certifies only when both mandatory denominator types pass every criterion', function (): void {
Spec425::syncDefaults();
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
'scopes_granted' => ['Policy.Read.All'],
]);
Spec425::createResourceWithEvidence($environment, $connection, 'conditionalAccessPolicy', Spec425::fixture('conditional-access', 'no-change'));
Spec425::createResourceWithEvidence($environment, $connection, 'securityDefaults', Spec425::fixture('security-defaults', 'no-change'));
bindFailHardGraphClient();
$result = assertNoOutboundHttp(fn (): EntraCertifiedComparePackResult => app(EntraCertifiedComparePackEvaluator::class)
->evaluate($environment, $connection));
expect($result->state())->toBe(EntraCertifiedComparePackResult::PASSED)
->and($result->certified())->toBeTrue()
->and($result->denominator())->toBe(['conditionalAccessPolicy', 'securityDefaults'])
->and($result->blockers())->toBe([])
->and(json_encode($result->toArray(), JSON_THROW_ON_ERROR))
->not->toContain('raw_payload')
->not->toContain('permission_context')
->not->toContain('Policy.Read.All');
});
it('Spec425 blocks certification when either denominator item is missing and does not fallback to wrong-scope evidence', function (): void {
Spec425::syncDefaults();
[, $environment] = createMinimalUserWithTenant(role: 'owner');
[, $foreignEnvironment] = createMinimalUserWithTenant(role: 'owner');
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
]);
$foreignConnection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $foreignEnvironment->workspace_id,
'managed_environment_id' => (int) $foreignEnvironment->getKey(),
]);
Spec425::createResourceWithEvidence($environment, $connection, 'conditionalAccessPolicy', Spec425::fixture('conditional-access', 'no-change'));
Spec425::createResourceWithEvidence($foreignEnvironment, $foreignConnection, 'securityDefaults', Spec425::fixture('security-defaults', 'no-change'));
$result = app(EntraCertifiedComparePackEvaluator::class)->evaluate($environment, $connection);
expect($result->state())->toBe(EntraCertifiedComparePackResult::BLOCKED_MISSING_EVIDENCE)
->and($result->certified())->toBeFalse()
->and($result->blockers())->toContain(EntraCertifiedComparePackResult::BLOCKED_MISSING_EVIDENCE);
});
it('Spec425 blocks stale or superseded latest evidence instead of falling back to first or latest implicitly', function (): void {
Spec425::syncDefaults();
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
]);
$resource = Spec425::createResourceWithEvidence($environment, $connection, 'conditionalAccessPolicy', Spec425::fixture('conditional-access', 'no-change'));
Spec425::createResourceWithEvidence($environment, $connection, 'securityDefaults', Spec425::fixture('security-defaults', 'no-change'));
$latestEvidence = $resource->latestEvidence()->firstOrFail();
TenantConfigurationResourceEvidence::factory()->create([
'resource_id' => (int) $resource->getKey(),
'workspace_id' => (int) $resource->workspace_id,
'managed_environment_id' => (int) $resource->managed_environment_id,
'provider_connection_id' => (int) $resource->provider_connection_id,
'resource_type_id' => (int) $resource->resource_type_id,
'operation_run_id' => (int) $latestEvidence->operation_run_id,
'source_contract_key' => 'conditionalAccessPolicy',
'source_endpoint' => '/identity/conditionalAccess/policies',
'source_version' => 'v1.0',
'raw_payload' => Spec425::fixture('conditional-access', 'state-change'),
'normalized_payload' => Spec425::normalizedPayload(Spec425::fixture('conditional-access', 'state-change')),
'payload_hash' => Spec425::payloadHash(Spec425::normalizedPayload(Spec425::fixture('conditional-access', 'state-change'))),
'evidence_state' => EvidenceState::ContentBacked->value,
'coverage_level' => CoverageLevel::Renderable->value,
'capture_outcome' => CaptureOutcome::Captured->value,
'captured_at' => $latestEvidence->captured_at->copy()->addMinute(),
]);
$result = app(EntraCertifiedComparePackEvaluator::class)->evaluate($environment, $connection);
$conditionalAccess = collect($result->resourceResults())->firstWhere('canonical_type', 'conditionalAccessPolicy');
$firstResource = collect($conditionalAccess['resources'])->first();
expect($result->state())->toBe(EntraCertifiedComparePackResult::BLOCKED_MISSING_EVIDENCE)
->and($firstResource['reasons'])->toContain('latest_evidence_not_current');
});
it('Spec425 blocks identity states that are not stable', function (): void {
Spec425::syncDefaults();
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
]);
Spec425::createResourceWithEvidence(
$environment,
$connection,
'conditionalAccessPolicy',
Spec425::fixture('conditional-access', 'no-change'),
['latest_identity_state' => IdentityState::Derived->value],
);
Spec425::createResourceWithEvidence($environment, $connection, 'securityDefaults', Spec425::fixture('security-defaults', 'no-change'));
$result = app(EntraCertifiedComparePackEvaluator::class)->evaluate($environment, $connection);
expect($result->state())->toBe(EntraCertifiedComparePackResult::BLOCKED_IDENTITY)
->and($result->blockers())->toContain(EntraCertifiedComparePackResult::BLOCKED_IDENTITY);
});
it('Spec425 blocks unsupported fields because they make certification ambiguous', function (): void {
Spec425::syncDefaults();
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
]);
Spec425::createResourceWithEvidence($environment, $connection, 'conditionalAccessPolicy', Spec425::fixture('conditional-access', 'unsupported-field'));
Spec425::createResourceWithEvidence($environment, $connection, 'securityDefaults', Spec425::fixture('security-defaults', 'no-change'));
$result = app(EntraCertifiedComparePackEvaluator::class)->evaluate($environment, $connection);
expect($result->state())->toBe(EntraCertifiedComparePackResult::BLOCKED_COMPARE)
->and($result->blockers())->toContain(EntraCertifiedComparePackResult::BLOCKED_COMPARE);
});
it('Spec425 blocks non-renderable denominator evidence', function (): void {
Spec425::syncDefaults();
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
]);
Spec425::createResourceWithEvidence(
$environment,
$connection,
'conditionalAccessPolicy',
Spec425::fixture('conditional-access', 'no-change'),
evidenceOverrides: ['coverage_level' => CoverageLevel::ContentBacked->value],
);
Spec425::createResourceWithEvidence($environment, $connection, 'securityDefaults', Spec425::fixture('security-defaults', 'no-change'));
$result = app(EntraCertifiedComparePackEvaluator::class)->evaluate($environment, $connection);
expect($result->state())->toBe(EntraCertifiedComparePackResult::BLOCKED_RENDER)
->and($result->blockers())->toContain(EntraCertifiedComparePackResult::BLOCKED_RENDER);
});
it('Spec425 blocks certification output when redaction would leak a sensitive value', function (): void {
Spec425::syncDefaults();
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
]);
$leakingPayload = array_replace(Spec425::fixture('security-defaults', 'no-change'), [
'clientSecret' => 'spec425-redaction-leak',
]);
$leakingNormalized = array_replace(Spec425::fixture('security-defaults', 'no-change'), [
'displayName' => 'spec425-redaction-leak',
]);
Spec425::createResourceWithEvidence($environment, $connection, 'conditionalAccessPolicy', Spec425::fixture('conditional-access', 'no-change'));
Spec425::createResourceWithEvidence(
$environment,
$connection,
'securityDefaults',
$leakingPayload,
evidenceOverrides: [
'normalized_payload' => $leakingNormalized,
'payload_hash' => Spec425::payloadHash($leakingNormalized),
],
);
$result = app(EntraCertifiedComparePackEvaluator::class)->evaluate($environment, $connection);
expect($result->state())->toBe(EntraCertifiedComparePackResult::BLOCKED_REDACTION)
->and($result->blockers())->toContain(EntraCertifiedComparePackResult::BLOCKED_REDACTION);
});
it('Spec425 rejects provider connections outside the managed environment scope', function (): void {
Spec425::syncDefaults();
[, $environment] = createMinimalUserWithTenant(role: 'owner');
[, $foreignEnvironment] = createMinimalUserWithTenant(role: 'owner');
$foreignConnection = ProviderConnection::factory()->withCredential()->create([
'workspace_id' => (int) $foreignEnvironment->workspace_id,
'managed_environment_id' => (int) $foreignEnvironment->getKey(),
]);
expect(fn () => app(EntraCertifiedComparePackEvaluator::class)->evaluate($environment, $foreignConnection))
->toThrow(InvalidArgumentException::class, 'Provider connection scope mismatch');
});