602 lines
24 KiB
PHP
602 lines
24 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\TenantConfiguration\ExchangePowerShellComparablePayloadBuilder;
|
|
use App\Services\TenantConfiguration\ExchangePowerShellCommandContracts;
|
|
use App\Services\TenantConfiguration\ExchangePowerShellTargetEvidenceNormalizer;
|
|
use App\Support\TenantConfiguration\CaptureOutcome;
|
|
use App\Support\TenantConfiguration\CoverageLevel;
|
|
use App\Support\TenantConfiguration\EvidenceState;
|
|
|
|
it('Spec437 builds deterministic comparable payloads from normalized payloads for each Exchange target', function (
|
|
string $canonicalType,
|
|
array $normalizedPayload,
|
|
array $expectedPaths,
|
|
array $forbiddenValues,
|
|
): void {
|
|
$normalizedPayload['raw_payload'] = ['secret' => 'spec437-raw-provider-secret'];
|
|
$normalizedPayload['raw_stdout'] = 'spec437-raw-stdout-secret';
|
|
$normalizedPayload['raw_stderr'] = 'spec437-raw-stderr-secret';
|
|
$normalizedPayload['transcript'] = 'spec437-transcript-secret';
|
|
$normalizedPayload['operation_run_context'] = ['token' => 'spec437-run-context-token'];
|
|
|
|
$result = app(ExchangePowerShellComparablePayloadBuilder::class)->build(
|
|
resourceType: $canonicalType,
|
|
normalizedPayload: $normalizedPayload,
|
|
payloadHash: spec437UnitPayloadHash(),
|
|
metadata: [
|
|
...spec437UnitEvidenceMetadata(),
|
|
'source_identity' => ['field' => 'metadata', 'value' => 'spec437-metadata-override'],
|
|
'source_endpoint' => 'exchange_online_powershell_rest:'.$canonicalType,
|
|
],
|
|
);
|
|
|
|
expect($result['comparable'])->toBeTrue()
|
|
->and($result['blocker'])->toBeNull()
|
|
->and($result['payload']['workload'])->toBe('exchange')
|
|
->and($result['payload']['resource_type'])->toBe($canonicalType)
|
|
->and($result['payload']['compare_input'])->toBe('normalized_payload_only')
|
|
->and($result['payload']['identity']['source_key'])->toBe(
|
|
$canonicalType.':'.data_get($normalizedPayload, 'source_identity.field').':'.spec437UnitIdentityHmac(
|
|
$canonicalType,
|
|
(string) data_get($normalizedPayload, 'source_identity.field'),
|
|
(string) data_get($normalizedPayload, 'source_identity.value'),
|
|
),
|
|
)
|
|
->and($result['payload']['identity']['value_hmac'])->toBe(spec437UnitIdentityHmac(
|
|
$canonicalType,
|
|
(string) data_get($normalizedPayload, 'source_identity.field'),
|
|
(string) data_get($normalizedPayload, 'source_identity.value'),
|
|
))
|
|
->and($result['payload']['provenance']['payload_hash'])->toBe(spec437UnitPayloadHash())
|
|
->and($result['payload']['source']['source_surface'])->toBe(ExchangePowerShellCommandContracts::SOURCE_SURFACE)
|
|
->and($result['payload']['source']['payload_shape_version'])->toBe(ExchangePowerShellTargetEvidenceNormalizer::PAYLOAD_SHAPE_VERSION);
|
|
|
|
foreach ($expectedPaths as $path => $value) {
|
|
expect(data_get($result['payload'], $path))->toBe($value);
|
|
}
|
|
|
|
$encoded = json_encode($result, JSON_THROW_ON_ERROR);
|
|
|
|
foreach ($forbiddenValues as $forbiddenValue) {
|
|
expect($encoded)->not->toContain($forbiddenValue);
|
|
}
|
|
|
|
expect($encoded)
|
|
->not->toContain('spec437-raw-provider-secret')
|
|
->not->toContain('spec437-raw-stdout-secret')
|
|
->not->toContain('spec437-raw-stderr-secret')
|
|
->not->toContain('spec437-transcript-secret')
|
|
->not->toContain('spec437-run-context-token')
|
|
->not->toContain('spec437-metadata-override')
|
|
->not->toContain((string) data_get($normalizedPayload, 'source_identity.value'));
|
|
})->with([
|
|
'transportRule' => [
|
|
'transportRule',
|
|
spec437UnitTransportRuleNormalizedPayload(),
|
|
[
|
|
'material.enabled' => true,
|
|
'material.mode' => 'Enforce',
|
|
'material.state' => 'Enabled',
|
|
'material.priority' => '0',
|
|
'conditions.sender_domain_is.redacted' => true,
|
|
'conditions.sender_domain_is.count' => 2,
|
|
'actions.delete_message' => true,
|
|
'actions.set_header_value.redacted' => true,
|
|
'exceptions.except_if_from.redacted' => true,
|
|
],
|
|
[
|
|
'contoso.example',
|
|
'fabrikam.example',
|
|
'subject-secret',
|
|
'header-secret',
|
|
'except-secret@example.com',
|
|
'spec437-rule-guid-1',
|
|
],
|
|
],
|
|
'remoteDomain' => [
|
|
'remoteDomain',
|
|
spec437UnitRemoteDomainNormalizedPayload(),
|
|
[
|
|
'remote_domain_class.is_default' => false,
|
|
'settings.auto_reply_enabled' => true,
|
|
'settings.auto_forward_enabled.redacted' => true,
|
|
'settings.mail_tips_access_scope.redacted' => true,
|
|
'protected_values.domain_name.redacted' => true,
|
|
],
|
|
[
|
|
'contoso.example',
|
|
'mailtips-secret',
|
|
'spec437-remote-domain-1',
|
|
],
|
|
],
|
|
'inboundConnector' => [
|
|
'inboundConnector',
|
|
spec437UnitInboundConnectorNormalizedPayload(),
|
|
[
|
|
'material.connector_type' => 'Partner',
|
|
'material.enabled' => true,
|
|
'material.require_tls' => true,
|
|
'routing.sender_ip_addresses.redacted' => true,
|
|
'routing.sender_ip_addresses.count' => 1,
|
|
'routing.smart_hosts.redacted' => true,
|
|
'routing.tls_sender_certificate_name.redacted' => true,
|
|
'routing.comment.redacted' => true,
|
|
],
|
|
[
|
|
'203.0.113.10',
|
|
'mail.contoso.example',
|
|
'CN=secret-cert',
|
|
'sensitive routing note',
|
|
'spec437-inbound-connector-1',
|
|
],
|
|
],
|
|
]);
|
|
|
|
it('Spec437 blocks unsupported targets missing normalized identity and unsafe remote-domain identity', function (
|
|
string $resourceType,
|
|
array $normalizedPayload,
|
|
string $expectedBlocker,
|
|
): void {
|
|
$result = app(ExchangePowerShellComparablePayloadBuilder::class)->build(
|
|
$resourceType,
|
|
$normalizedPayload,
|
|
payloadHash: spec437UnitPayloadHash(),
|
|
metadata: spec437UnitEvidenceMetadata(),
|
|
);
|
|
|
|
expect($result['comparable'])->toBeFalse()
|
|
->and($result['blocker'])->toBe($expectedBlocker)
|
|
->and($result['payload'])->toBeNull();
|
|
})->with([
|
|
'unsupported target' => ['acceptedDomain', spec437UnitTransportRuleNormalizedPayload(), 'unsupported_resource_type'],
|
|
'missing normalized payload' => ['transportRule', [], 'missing_normalized_payload'],
|
|
'wrong canonical type' => ['transportRule', spec437UnitRemoteDomainNormalizedPayload(), 'normalized_payload_type_mismatch'],
|
|
'missing source identity' => ['transportRule', array_diff_key(spec437UnitTransportRuleNormalizedPayload(), ['source_identity' => true]), 'missing_stable_identity'],
|
|
'remote domain domain-only identity' => [
|
|
'remoteDomain',
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'source_identity' => ['field' => 'DomainName', 'value' => 'contoso.example'],
|
|
]),
|
|
'unsafe_identity',
|
|
],
|
|
'remote domain display-only identity' => [
|
|
'remoteDomain',
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'source_identity' => ['field' => 'DisplayName', 'value' => 'Contoso remote domain'],
|
|
]),
|
|
'unsafe_identity',
|
|
],
|
|
'remote domain snake-case domain identity' => [
|
|
'remoteDomain',
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'source_identity' => ['field' => 'domain_name', 'value' => 'contoso.example'],
|
|
]),
|
|
'unsafe_identity',
|
|
],
|
|
'remote domain uppercase display identity' => [
|
|
'remoteDomain',
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'source_identity' => ['field' => 'DISPLAY_NAME', 'value' => 'Contoso remote domain'],
|
|
]),
|
|
'unsafe_identity',
|
|
],
|
|
]);
|
|
|
|
it('Spec437 blocks non content-backed or uncaptured evidence provenance', function (
|
|
?string $payloadHash,
|
|
array $metadata,
|
|
string $expectedBlocker,
|
|
): void {
|
|
$result = app(ExchangePowerShellComparablePayloadBuilder::class)->build(
|
|
'transportRule',
|
|
spec437UnitTransportRuleNormalizedPayload(),
|
|
payloadHash: $payloadHash,
|
|
metadata: $metadata,
|
|
);
|
|
|
|
expect($result['comparable'])->toBeFalse()
|
|
->and($result['blocker'])->toBe($expectedBlocker)
|
|
->and($result['payload'])->toBeNull();
|
|
})->with([
|
|
'missing payload hash' => [null, spec437UnitEvidenceMetadata(), 'missing_payload_hash'],
|
|
'invalid payload hash' => ['spec437-source-payload-hash', spec437UnitEvidenceMetadata(), 'invalid_payload_hash'],
|
|
'wrong evidence state' => [
|
|
spec437UnitPayloadHash(),
|
|
[...spec437UnitEvidenceMetadata(), 'evidence_state' => EvidenceState::NotCaptured->value],
|
|
'evidence_not_content_backed',
|
|
],
|
|
'wrong coverage level' => [
|
|
spec437UnitPayloadHash(),
|
|
[...spec437UnitEvidenceMetadata(), 'coverage_level' => CoverageLevel::Detected->value],
|
|
'coverage_not_content_backed',
|
|
],
|
|
'wrong capture outcome' => [
|
|
spec437UnitPayloadHash(),
|
|
[...spec437UnitEvidenceMetadata(), 'capture_outcome' => CaptureOutcome::BlockedUnsupported->value],
|
|
'capture_not_captured',
|
|
],
|
|
]);
|
|
|
|
it('Spec437 blocks unsafe exact values in protected comparable sections', function (
|
|
string $resourceType,
|
|
array $normalizedPayload,
|
|
string $forbiddenValue,
|
|
): void {
|
|
$result = app(ExchangePowerShellComparablePayloadBuilder::class)->build(
|
|
$resourceType,
|
|
$normalizedPayload,
|
|
payloadHash: spec437UnitPayloadHash(),
|
|
metadata: spec437UnitEvidenceMetadata(),
|
|
);
|
|
$encoded = json_encode($result, JSON_THROW_ON_ERROR);
|
|
|
|
expect($result['comparable'])->toBeFalse()
|
|
->and($result['blocker'])->toBe('unsafe_exact_value')
|
|
->and($result['payload'])->toBeNull()
|
|
->and($encoded)->not->toContain($forbiddenValue);
|
|
})->with([
|
|
'transportRule raw condition' => [
|
|
'transportRule',
|
|
array_replace_recursive(spec437UnitTransportRuleNormalizedPayload(), [
|
|
'conditions' => ['sender_domain_is' => 'contoso.example'],
|
|
]),
|
|
'contoso.example',
|
|
],
|
|
'transportRule raw action' => [
|
|
'transportRule',
|
|
array_replace_recursive(spec437UnitTransportRuleNormalizedPayload(), [
|
|
'actions' => ['set_header_value' => 'header-secret'],
|
|
]),
|
|
'header-secret',
|
|
],
|
|
'remoteDomain raw setting' => [
|
|
'remoteDomain',
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'settings' => ['mail_tips_access_scope' => 'mailtips-secret'],
|
|
]),
|
|
'mailtips-secret',
|
|
],
|
|
'remoteDomain raw protected value' => [
|
|
'remoteDomain',
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'protected_configuration' => ['domain_name' => 'contoso.example'],
|
|
]),
|
|
'contoso.example',
|
|
],
|
|
'inboundConnector raw routing' => [
|
|
'inboundConnector',
|
|
array_replace_recursive(spec437UnitInboundConnectorNormalizedPayload(), [
|
|
'routing' => ['sender_ip_addresses' => '203.0.113.10'],
|
|
]),
|
|
'203.0.113.10',
|
|
],
|
|
'inboundConnector raw protected value' => [
|
|
'inboundConnector',
|
|
array_replace_recursive(spec437UnitInboundConnectorNormalizedPayload(), [
|
|
'protected_configuration' => ['sender_ip_addresses' => '203.0.113.10'],
|
|
]),
|
|
'203.0.113.10',
|
|
],
|
|
]);
|
|
|
|
it('Spec437 blocks malformed exact values on allowlisted comparable fields', function (
|
|
string $resourceType,
|
|
array $normalizedPayload,
|
|
string $forbiddenValue,
|
|
): void {
|
|
$result = app(ExchangePowerShellComparablePayloadBuilder::class)->build(
|
|
$resourceType,
|
|
$normalizedPayload,
|
|
payloadHash: spec437UnitPayloadHash(),
|
|
metadata: spec437UnitEvidenceMetadata(),
|
|
);
|
|
$encoded = json_encode($result, JSON_THROW_ON_ERROR);
|
|
|
|
expect($result['comparable'])->toBeFalse()
|
|
->and($result['blocker'])->toBe('unsafe_exact_value')
|
|
->and($result['payload'])->toBeNull()
|
|
->and($encoded)->not->toContain($forbiddenValue);
|
|
})->with([
|
|
'transportRule malformed bool action' => [
|
|
'transportRule',
|
|
array_replace_recursive(spec437UnitTransportRuleNormalizedPayload(), [
|
|
'actions' => ['delete_message' => 'header-secret'],
|
|
]),
|
|
'header-secret',
|
|
],
|
|
'transportRule malformed enum action' => [
|
|
'transportRule',
|
|
array_replace_recursive(spec437UnitTransportRuleNormalizedPayload(), [
|
|
'actions' => ['apply_html_disclaimer_fallback_action' => 'secret-fallback'],
|
|
]),
|
|
'secret-fallback',
|
|
],
|
|
'transportRule malformed priority' => [
|
|
'transportRule',
|
|
array_replace_recursive(spec437UnitTransportRuleNormalizedPayload(), [
|
|
'rule_order' => ['priority' => 'priority-secret'],
|
|
]),
|
|
'priority-secret',
|
|
],
|
|
'remoteDomain malformed bool setting' => [
|
|
'remoteDomain',
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'settings' => ['auto_reply_enabled' => 'mailtips-secret'],
|
|
]),
|
|
'mailtips-secret',
|
|
],
|
|
'inboundConnector malformed enum routing' => [
|
|
'inboundConnector',
|
|
array_replace_recursive(spec437UnitInboundConnectorNormalizedPayload(), [
|
|
'routing' => ['connector_type' => 'mail.contoso.example'],
|
|
]),
|
|
'mail.contoso.example',
|
|
],
|
|
'inboundConnector malformed bool material' => [
|
|
'inboundConnector',
|
|
array_replace_recursive(spec437UnitInboundConnectorNormalizedPayload(), [
|
|
'material' => ['RequireTls' => 'tls-secret'],
|
|
]),
|
|
'tls-secret',
|
|
],
|
|
]);
|
|
|
|
it('Spec437 comparable hash changes only for material or safe redacted-state changes', function (): void {
|
|
$builder = app(ExchangePowerShellComparablePayloadBuilder::class);
|
|
$base = spec437UnitTransportRuleNormalizedPayload();
|
|
$volatileOnly = array_replace_recursive($base, [
|
|
'diagnostics' => [
|
|
'volatile_fields' => ['RunspaceId', 'WhenChanged', 'ExchangeVersion'],
|
|
],
|
|
'runtime_context' => [
|
|
'WhenChanged' => '2026-07-09T10:00:00Z',
|
|
],
|
|
]);
|
|
$materialChanged = array_replace_recursive($base, [
|
|
'material' => [
|
|
'Enabled' => false,
|
|
],
|
|
]);
|
|
$redactedExactChangedSameCount = array_replace_recursive($base, [
|
|
'conditions' => [
|
|
'sender_domain_is' => spec437UnitRedactedMarker(count: 2),
|
|
],
|
|
]);
|
|
$redactedCountChanged = array_replace_recursive($base, [
|
|
'conditions' => [
|
|
'sender_domain_is' => spec437UnitRedactedMarker(count: 3),
|
|
],
|
|
]);
|
|
|
|
$baseResult = spec437UnitBuild($builder, 'transportRule', $base);
|
|
|
|
expect($baseResult['comparable_hash'])->toBe(spec437UnitBuild($builder, 'transportRule', $volatileOnly)['comparable_hash'])
|
|
->and($baseResult['comparable_hash'])->not->toBe(spec437UnitBuild($builder, 'transportRule', $materialChanged)['comparable_hash'])
|
|
->and($baseResult['comparable_hash'])->toBe(spec437UnitBuild($builder, 'transportRule', $redactedExactChangedSameCount)['comparable_hash'])
|
|
->and($baseResult['comparable_hash'])->not->toBe(spec437UnitBuild($builder, 'transportRule', $redactedCountChanged)['comparable_hash']);
|
|
});
|
|
|
|
it('Spec437 comparable hash behavior is deterministic for remoteDomain and inboundConnector targets', function (
|
|
string $resourceType,
|
|
array $base,
|
|
array $volatileOnly,
|
|
array $materialChanged,
|
|
array $redactedCountChanged,
|
|
): void {
|
|
$builder = app(ExchangePowerShellComparablePayloadBuilder::class);
|
|
$baseResult = spec437UnitBuild($builder, $resourceType, $base);
|
|
|
|
expect($baseResult['comparable_hash'])->toBe(spec437UnitBuild($builder, $resourceType, $volatileOnly)['comparable_hash'])
|
|
->and($baseResult['comparable_hash'])->not->toBe(spec437UnitBuild($builder, $resourceType, $materialChanged)['comparable_hash'])
|
|
->and($baseResult['comparable_hash'])->not->toBe(spec437UnitBuild($builder, $resourceType, $redactedCountChanged)['comparable_hash']);
|
|
})->with([
|
|
'remoteDomain' => [
|
|
'remoteDomain',
|
|
spec437UnitRemoteDomainNormalizedPayload(),
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'diagnostics' => ['volatile_fields' => ['RunspaceId', 'WhenChanged', 'ExchangeVersion']],
|
|
'runtime_context' => ['WhenChanged' => '2026-07-09T10:00:00Z'],
|
|
]),
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'settings' => ['auto_reply_enabled' => false],
|
|
]),
|
|
array_replace_recursive(spec437UnitRemoteDomainNormalizedPayload(), [
|
|
'protected_configuration' => ['domain_name' => spec437UnitRedactedMarker(count: 2)],
|
|
]),
|
|
],
|
|
'inboundConnector' => [
|
|
'inboundConnector',
|
|
spec437UnitInboundConnectorNormalizedPayload(),
|
|
array_replace_recursive(spec437UnitInboundConnectorNormalizedPayload(), [
|
|
'diagnostics' => ['volatile_fields' => ['RunspaceId', 'WhenChanged', 'ExchangeVersion']],
|
|
'runtime_context' => ['WhenChanged' => '2026-07-09T10:00:00Z'],
|
|
]),
|
|
array_replace_recursive(spec437UnitInboundConnectorNormalizedPayload(), [
|
|
'material' => ['RequireTls' => false],
|
|
'routing' => ['require_tls' => false],
|
|
]),
|
|
array_replace_recursive(spec437UnitInboundConnectorNormalizedPayload(), [
|
|
'routing' => ['sender_ip_addresses' => spec437UnitRedactedMarker(count: 2)],
|
|
'protected_configuration' => ['sender_ip_addresses' => spec437UnitRedactedMarker(count: 2)],
|
|
]),
|
|
],
|
|
]);
|
|
|
|
function spec437UnitTransportRuleNormalizedPayload(): 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' => [
|
|
'DisplayName' => 'Spec 437 transport rule',
|
|
'Enabled' => true,
|
|
'Mode' => 'Enforce',
|
|
'State' => 'Enabled',
|
|
],
|
|
'rule_order' => [
|
|
'priority' => '0',
|
|
'order_is_identity' => false,
|
|
],
|
|
'conditions' => [
|
|
'sender_domain_is' => spec437UnitRedactedMarker(count: 2),
|
|
'subject_contains_words' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'actions' => [
|
|
'delete_message' => true,
|
|
'set_header_value' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'exceptions' => [
|
|
'except_if_from' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'protected_configuration' => [
|
|
'sender_domain_is' => spec437UnitRedactedMarker(count: 2),
|
|
],
|
|
'sensitive_content' => [
|
|
'subject_contains_words' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'diagnostics' => [
|
|
'volatile_fields' => ['WhenChanged', 'ExchangeVersion', 'RunspaceId'],
|
|
'protected_fields' => ['sender_domain_is'],
|
|
'sensitive_fields' => ['subject_contains_words'],
|
|
],
|
|
'source' => spec437UnitSourceMetadata('transportRule', 'Get-TransportRule', 'exchange-transport-rule-readiness-v1'),
|
|
];
|
|
}
|
|
|
|
function spec437UnitRemoteDomainNormalizedPayload(): 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' => [
|
|
'Identity' => 'spec437-remote-domain-1',
|
|
'IsDefault' => false,
|
|
],
|
|
'remote_domain_class' => [
|
|
'domain_name_is_identity' => false,
|
|
'is_default' => false,
|
|
],
|
|
'settings' => [
|
|
'auto_reply_enabled' => true,
|
|
'auto_forward_enabled' => spec437UnitRedactedMarker(count: 1),
|
|
'mail_tips_access_scope' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'protected_configuration' => [
|
|
'domain_name' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'sensitive_content' => [
|
|
'mail_tips_access_scope' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'diagnostics' => [
|
|
'volatile_fields' => ['WhenChanged', 'ExchangeVersion', 'RunspaceId'],
|
|
'protected_fields' => ['domain_name'],
|
|
'sensitive_fields' => ['mail_tips_access_scope'],
|
|
],
|
|
'source' => spec437UnitSourceMetadata('remoteDomain', 'Get-RemoteDomain', 'exchange-remote-domain-readiness-v1'),
|
|
];
|
|
}
|
|
|
|
function spec437UnitInboundConnectorNormalizedPayload(): array
|
|
{
|
|
return [
|
|
'canonical_type' => 'inboundConnector',
|
|
'source_surface' => ExchangePowerShellCommandContracts::SOURCE_SURFACE,
|
|
'payload_shape_version' => ExchangePowerShellTargetEvidenceNormalizer::PAYLOAD_SHAPE_VERSION,
|
|
'normalizer_version' => 'exchange-inbound-connector-readiness-v1',
|
|
'source_identity' => ['field' => 'ConnectorId', 'value' => 'spec437-inbound-connector-1'],
|
|
'material' => [
|
|
'ConnectorType' => 'Partner',
|
|
'Enabled' => true,
|
|
'RequireTls' => true,
|
|
],
|
|
'routing' => [
|
|
'connector_type' => 'Partner',
|
|
'enabled' => true,
|
|
'require_tls' => true,
|
|
'sender_ip_addresses' => spec437UnitRedactedMarker(count: 1),
|
|
'smart_hosts' => spec437UnitRedactedMarker(count: 1),
|
|
'tls_sender_certificate_name' => spec437UnitRedactedMarker(count: 1),
|
|
'comment' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'protected_configuration' => [
|
|
'sender_ip_addresses' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'sensitive_content' => [
|
|
'smart_hosts' => spec437UnitRedactedMarker(count: 1),
|
|
'tls_sender_certificate_name' => spec437UnitRedactedMarker(count: 1),
|
|
'comment' => spec437UnitRedactedMarker(count: 1),
|
|
],
|
|
'diagnostics' => [
|
|
'volatile_fields' => ['WhenChanged', 'ExchangeVersion', 'RunspaceId'],
|
|
'protected_fields' => ['sender_ip_addresses'],
|
|
'sensitive_fields' => ['smart_hosts', 'tls_sender_certificate_name', 'comment'],
|
|
],
|
|
'source' => spec437UnitSourceMetadata('inboundConnector', 'Get-InboundConnector', 'exchange-inbound-connector-readiness-v1'),
|
|
];
|
|
}
|
|
|
|
function spec437UnitRedactedMarker(int $count = 1, bool $present = true): array
|
|
{
|
|
return [
|
|
'value' => '[redacted]',
|
|
'present' => $present,
|
|
'count' => $count,
|
|
];
|
|
}
|
|
|
|
function spec437UnitSourceMetadata(string $canonicalType, string $commandName, string $normalizerVersion): array
|
|
{
|
|
return [
|
|
'source_surface' => ExchangePowerShellCommandContracts::SOURCE_SURFACE,
|
|
'source_contract_key' => 'exchange_powershell.'.$canonicalType,
|
|
'command_contract_name' => $commandName,
|
|
'command_contract_version' => ExchangePowerShellCommandContracts::COMMAND_CONTRACT_VERSION,
|
|
'payload_shape_version' => ExchangePowerShellTargetEvidenceNormalizer::PAYLOAD_SHAPE_VERSION,
|
|
'normalizer_version' => $normalizerVersion,
|
|
];
|
|
}
|
|
|
|
function spec437UnitEvidenceMetadata(): array
|
|
{
|
|
return [
|
|
'evidence_state' => EvidenceState::ContentBacked->value,
|
|
'coverage_level' => CoverageLevel::ContentBacked->value,
|
|
'capture_outcome' => CaptureOutcome::Captured->value,
|
|
];
|
|
}
|
|
|
|
function spec437UnitBuild(ExchangePowerShellComparablePayloadBuilder $builder, string $resourceType, array $normalizedPayload): array
|
|
{
|
|
return $builder->build(
|
|
$resourceType,
|
|
$normalizedPayload,
|
|
payloadHash: spec437UnitPayloadHash(),
|
|
metadata: spec437UnitEvidenceMetadata(),
|
|
);
|
|
}
|
|
|
|
function spec437UnitPayloadHash(): string
|
|
{
|
|
return str_repeat('a', 64);
|
|
}
|
|
|
|
function spec437UnitIdentityHmac(string $resourceType, string $field, string $value): string
|
|
{
|
|
$key = (string) config('app.key');
|
|
|
|
if (str_starts_with($key, 'base64:')) {
|
|
$decoded = base64_decode(substr($key, 7), true);
|
|
$key = $decoded !== false ? $decoded : '';
|
|
}
|
|
|
|
return hash_hmac('sha256', $resourceType."\0".$field."\0".$value, $key);
|
|
}
|