274 lines
14 KiB
PHP
274 lines
14 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\TenantConfigurationResource;
|
|
use App\Models\TenantConfigurationResourceEvidence;
|
|
use App\Models\TenantConfigurationResourceType;
|
|
use App\Services\TenantConfiguration\ExchangePowerShellComparablePayloadBuilder;
|
|
use App\Services\TenantConfiguration\ExchangePowerShellCommandContracts;
|
|
use App\Services\TenantConfiguration\ExchangePowerShellTargetEvidenceNormalizer;
|
|
use App\Services\TenantConfiguration\ResourceTypeRegistry;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\TenantConfiguration\CaptureOutcome;
|
|
use App\Support\TenantConfiguration\ClaimState;
|
|
use App\Support\TenantConfiguration\CoverageLevel;
|
|
use App\Support\TenantConfiguration\EvidenceState;
|
|
use App\Support\TenantConfiguration\IdentityState;
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
beforeEach(function (): void {
|
|
app(ResourceTypeRegistry::class)->syncDefaults();
|
|
});
|
|
|
|
it('Spec437 derives comparable payloads without mutating evidence resource resource-type or OperationRun truth', function (): void {
|
|
[$user, $environment] = createMinimalUserWithTenant(role: 'owner');
|
|
$connection = ProviderConnection::factory()->withCredential()->create([
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
]);
|
|
$resourceType = spec437FeatureResourceType('transportRule');
|
|
$operationRun = OperationRun::factory()->withUser($user)->forTenant($environment)->create([
|
|
'type' => OperationRunType::TenantConfigurationCapture->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Succeeded->value,
|
|
'context' => [
|
|
'target_scope' => [
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
],
|
|
'resource_types' => ['transportRule'],
|
|
],
|
|
]);
|
|
$resource = TenantConfigurationResource::factory()->create([
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'resource_type_id' => (int) $resourceType->getKey(),
|
|
'canonical_type' => 'transportRule',
|
|
'latest_evidence_state' => EvidenceState::ContentBacked->value,
|
|
'latest_identity_state' => IdentityState::Stable->value,
|
|
'latest_claim_state' => ClaimState::InternalOnly->value,
|
|
'latest_payload_hash' => spec437FeaturePayloadHash(),
|
|
]);
|
|
$evidence = TenantConfigurationResourceEvidence::factory()->create([
|
|
'resource_id' => (int) $resource->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'resource_type_id' => (int) $resourceType->getKey(),
|
|
'operation_run_id' => (int) $operationRun->getKey(),
|
|
'source_contract_key' => 'exchange_powershell.transportRule',
|
|
'source_endpoint' => 'exchange_online_powershell_rest:Get-TransportRule',
|
|
'source_metadata' => ['spec' => 437, 'source_identity' => 'must-not-override'],
|
|
'raw_payload' => ['Guid' => 'spec437-rule-guid-1', 'SubjectContainsWords' => ['secret-subject']],
|
|
'normalized_payload' => spec437FeatureTransportRuleNormalizedPayload(),
|
|
'payload_hash' => spec437FeaturePayloadHash(),
|
|
'permission_context' => ['provider_connection_id' => (int) $connection->getKey()],
|
|
'evidence_state' => EvidenceState::ContentBacked->value,
|
|
'coverage_level' => CoverageLevel::ContentBacked->value,
|
|
'capture_outcome' => CaptureOutcome::Captured->value,
|
|
]);
|
|
$resource->forceFill([
|
|
'latest_evidence_id' => (int) $evidence->getKey(),
|
|
'latest_payload_hash' => spec437FeaturePayloadHash(),
|
|
])->save();
|
|
|
|
$beforeEvidence = $evidence->fresh();
|
|
$beforeResource = $resource->fresh();
|
|
$beforeResourceType = $resourceType->fresh();
|
|
$beforeOperationRun = $operationRun->fresh();
|
|
|
|
$result = app(ExchangePowerShellComparablePayloadBuilder::class)->build(
|
|
resourceType: 'transportRule',
|
|
normalizedPayload: $beforeEvidence->normalized_payload,
|
|
payloadHash: $beforeEvidence->payload_hash,
|
|
metadata: [
|
|
'source_contract_key' => $beforeEvidence->source_contract_key,
|
|
'source_endpoint' => $beforeEvidence->source_endpoint,
|
|
'evidence_state' => $beforeEvidence->evidence_state->value,
|
|
'coverage_level' => $beforeEvidence->coverage_level->value,
|
|
'capture_outcome' => $beforeEvidence->capture_outcome->value,
|
|
'source_identity' => ['field' => 'metadata', 'value' => 'unsafe-override'],
|
|
],
|
|
);
|
|
|
|
$afterEvidence = $evidence->fresh();
|
|
$afterResource = $resource->fresh();
|
|
$afterResourceType = $resourceType->fresh();
|
|
$afterOperationRun = $operationRun->fresh();
|
|
|
|
expect($result['comparable'])->toBeTrue()
|
|
->and($result['payload']['provenance']['coverage_level'])->toBe(CoverageLevel::ContentBacked->value)
|
|
->and($result['payload']['identity']['field'])->toBe('Guid')
|
|
->and($afterEvidence->raw_payload)->toBe($beforeEvidence->raw_payload)
|
|
->and($afterEvidence->normalized_payload)->toBe($beforeEvidence->normalized_payload)
|
|
->and($afterEvidence->payload_hash)->toBe($beforeEvidence->payload_hash)
|
|
->and($afterEvidence->operation_run_id)->toBe($beforeEvidence->operation_run_id)
|
|
->and($afterEvidence->source_metadata)->toBe($beforeEvidence->source_metadata)
|
|
->and($afterEvidence->coverage_level)->toBe(CoverageLevel::ContentBacked)
|
|
->and($afterResource->latest_evidence_id)->toBe($beforeResource->latest_evidence_id)
|
|
->and($afterResource->latest_evidence_state)->toBe($beforeResource->latest_evidence_state)
|
|
->and($afterResource->latest_claim_state)->toBe($beforeResource->latest_claim_state)
|
|
->and($afterResource->latest_payload_hash)->toBe($beforeResource->latest_payload_hash)
|
|
->and($afterResourceType->default_coverage_level)->toBe($beforeResourceType->default_coverage_level)
|
|
->and($afterOperationRun->type)->toBe($beforeOperationRun->type)
|
|
->and($afterOperationRun->context)->toBe($beforeOperationRun->context);
|
|
});
|
|
|
|
it('Spec437 returns no comparable artifact for missing unsafe or unsupported evidence input', function (): void {
|
|
$builder = app(ExchangePowerShellComparablePayloadBuilder::class);
|
|
$missing = $builder->build('transportRule', []);
|
|
$unsafe = $builder->build('remoteDomain', array_replace_recursive(spec437FeatureRemoteDomainNormalizedPayload(), [
|
|
'source_identity' => ['field' => 'DomainName', 'value' => 'contoso.example'],
|
|
]), payloadHash: spec437FeaturePayloadHash(), metadata: spec437FeatureEvidenceMetadata());
|
|
$unsupported = $builder->build('outboundConnector', spec437FeatureRemoteDomainNormalizedPayload());
|
|
|
|
expect($missing['comparable'])->toBeFalse()
|
|
->and($missing['payload'])->toBeNull()
|
|
->and($unsafe['comparable'])->toBeFalse()
|
|
->and($unsafe['payload'])->toBeNull()
|
|
->and($unsupported['comparable'])->toBeFalse()
|
|
->and($unsupported['payload'])->toBeNull()
|
|
->and(TenantConfigurationResource::query()->count())->toBe(0)
|
|
->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0);
|
|
});
|
|
|
|
it('Spec437 introduces no product surface trigger persisted compare table tenant-id ownership or OperationRun type', function (): void {
|
|
$builderSource = file_get_contents(app_path('Services/TenantConfiguration/ExchangePowerShellComparablePayloadBuilder.php')) ?: '';
|
|
$operationTypes = OperationRunType::values();
|
|
|
|
expect($builderSource)
|
|
->not->toContain('GenericPayloadNormalizer')
|
|
->not->toContain('ExchangeTeamsComparablePayloadNormalizer')
|
|
->not->toContain('CoverageEvidenceWriter')
|
|
->not->toContain('TenantConfigurationResourceEvidence::')
|
|
->and(Schema::hasColumn('tenant_configuration_resources', 'tenant_id'))->toBeFalse()
|
|
->and(Schema::hasColumn('tenant_configuration_resource_evidence', 'tenant_id'))->toBeFalse()
|
|
->and(glob(app_path('Filament/**/*ExchangePowerShellComparable*')) ?: [])->toBe([])
|
|
->and(glob(app_path('Livewire/**/*ExchangePowerShellComparable*')) ?: [])->toBe([])
|
|
->and(glob(base_path('routes/*exchange*comparable*')) ?: [])->toBe([])
|
|
->and(glob(resource_path('views/**/*ExchangePowerShellComparable*')) ?: [])->toBe([])
|
|
->and(glob(database_path('migrations/*exchange*comparable*')) ?: [])->toBe([])
|
|
->and(glob(database_path('migrations/*tenant*configuration*compare*')) ?: [])->toBe([])
|
|
->and(File::exists(app_path('Jobs/TenantConfiguration/BuildExchangePowerShellComparablePayloadJob.php')))->toBeFalse()
|
|
->and(File::exists(app_path('Console/Commands/BuildExchangePowerShellComparablePayload.php')))->toBeFalse()
|
|
->and($operationTypes)
|
|
->not->toContain('tenant_configuration.exchange_comparable')
|
|
->not->toContain('tenant_configuration.exchange_powershell_compare')
|
|
->not->toContain('tenant_configuration.compare');
|
|
});
|
|
|
|
function spec437FeatureResourceType(string $canonicalType): TenantConfigurationResourceType
|
|
{
|
|
return TenantConfigurationResourceType::query()
|
|
->where('canonical_type', $canonicalType)
|
|
->firstOrFail();
|
|
}
|
|
|
|
function spec437FeatureTransportRuleNormalizedPayload(): array
|
|
{
|
|
return [
|
|
'canonical_type' => 'transportRule',
|
|
'source_surface' => ExchangePowerShellCommandContracts::SOURCE_SURFACE,
|
|
'payload_shape_version' => ExchangePowerShellTargetEvidenceNormalizer::PAYLOAD_SHAPE_VERSION,
|
|
'normalizer_version' => 'exchange-transport-rule-readiness-v1',
|
|
'source_identity' => ['field' => 'Guid', 'value' => 'spec437-rule-guid-1'],
|
|
'material' => [
|
|
'Enabled' => true,
|
|
'Mode' => 'Enforce',
|
|
'State' => 'Enabled',
|
|
],
|
|
'rule_order' => ['priority' => '0'],
|
|
'conditions' => [
|
|
'sender_domain_is' => spec437FeatureRedactedMarker(count: 1),
|
|
'subject_contains_words' => spec437FeatureRedactedMarker(count: 1),
|
|
],
|
|
'actions' => [
|
|
'delete_message' => true,
|
|
'set_header_value' => spec437FeatureRedactedMarker(count: 1),
|
|
],
|
|
'exceptions' => [],
|
|
'protected_configuration' => [
|
|
'sender_domain_is' => spec437FeatureRedactedMarker(count: 1),
|
|
],
|
|
'sensitive_content' => [
|
|
'subject_contains_words' => spec437FeatureRedactedMarker(count: 1),
|
|
],
|
|
'diagnostics' => [
|
|
'volatile_fields' => ['WhenChanged', 'ExchangeVersion', 'RunspaceId'],
|
|
'protected_fields' => ['sender_domain_is'],
|
|
'sensitive_fields' => ['subject_contains_words'],
|
|
],
|
|
'source' => [
|
|
'source_surface' => ExchangePowerShellCommandContracts::SOURCE_SURFACE,
|
|
'source_contract_key' => 'exchange_powershell.transportRule',
|
|
'command_contract_name' => 'Get-TransportRule',
|
|
'command_contract_version' => ExchangePowerShellCommandContracts::COMMAND_CONTRACT_VERSION,
|
|
'payload_shape_version' => ExchangePowerShellTargetEvidenceNormalizer::PAYLOAD_SHAPE_VERSION,
|
|
'normalizer_version' => 'exchange-transport-rule-readiness-v1',
|
|
],
|
|
];
|
|
}
|
|
|
|
function spec437FeatureRemoteDomainNormalizedPayload(): array
|
|
{
|
|
return [
|
|
'canonical_type' => 'remoteDomain',
|
|
'source_surface' => ExchangePowerShellCommandContracts::SOURCE_SURFACE,
|
|
'payload_shape_version' => ExchangePowerShellTargetEvidenceNormalizer::PAYLOAD_SHAPE_VERSION,
|
|
'normalizer_version' => 'exchange-remote-domain-readiness-v1',
|
|
'source_identity' => ['field' => 'Identity', 'value' => 'spec437-remote-domain-1'],
|
|
'material' => ['IsDefault' => false],
|
|
'remote_domain_class' => [
|
|
'domain_name_is_identity' => false,
|
|
'is_default' => false,
|
|
],
|
|
'settings' => [
|
|
'auto_reply_enabled' => true,
|
|
'auto_forward_enabled' => spec437FeatureRedactedMarker(count: 1),
|
|
],
|
|
'protected_configuration' => [
|
|
'domain_name' => spec437FeatureRedactedMarker(count: 1),
|
|
],
|
|
'sensitive_content' => [],
|
|
'source' => [
|
|
'source_surface' => ExchangePowerShellCommandContracts::SOURCE_SURFACE,
|
|
'source_contract_key' => 'exchange_powershell.remoteDomain',
|
|
'command_contract_name' => 'Get-RemoteDomain',
|
|
'command_contract_version' => ExchangePowerShellCommandContracts::COMMAND_CONTRACT_VERSION,
|
|
'payload_shape_version' => ExchangePowerShellTargetEvidenceNormalizer::PAYLOAD_SHAPE_VERSION,
|
|
'normalizer_version' => 'exchange-remote-domain-readiness-v1',
|
|
],
|
|
];
|
|
}
|
|
|
|
function spec437FeatureRedactedMarker(int $count = 1, bool $present = true): array
|
|
{
|
|
return [
|
|
'value' => '[redacted]',
|
|
'present' => $present,
|
|
'count' => $count,
|
|
];
|
|
}
|
|
|
|
function spec437FeatureEvidenceMetadata(): array
|
|
{
|
|
return [
|
|
'evidence_state' => EvidenceState::ContentBacked->value,
|
|
'coverage_level' => CoverageLevel::ContentBacked->value,
|
|
'capture_outcome' => CaptureOutcome::Captured->value,
|
|
];
|
|
}
|
|
|
|
function spec437FeaturePayloadHash(): string
|
|
{
|
|
return str_repeat('b', 64);
|
|
}
|