Implements spec 425 with Entra certified compare pack support, coverage, guards, evaluator, fixtures, and tests. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #492
205 lines
8.5 KiB
PHP
205 lines
8.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Support\TenantConfiguration;
|
|
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\TenantConfigurationResource;
|
|
use App\Models\TenantConfigurationResourceEvidence;
|
|
use App\Models\TenantConfigurationResourceType;
|
|
use App\Services\TenantConfiguration\CoveragePayloadRedactor;
|
|
use App\Services\TenantConfiguration\ResourceTypeRegistry;
|
|
use App\Services\TenantConfiguration\SupportedScopeResolver;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\TenantConfiguration\CanonicalKeyKind;
|
|
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;
|
|
|
|
final class Spec425Fixtures
|
|
{
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function fixture(string $family, string $name): array
|
|
{
|
|
$path = base_path("tests/Fixtures/TenantConfiguration/Spec425/{$family}/{$name}.json");
|
|
$json = file_get_contents($path);
|
|
|
|
if ($json === false) {
|
|
throw new \RuntimeException("Missing Spec425 fixture [{$family}/{$name}].");
|
|
}
|
|
|
|
$payload = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
|
|
|
|
if (! is_array($payload)) {
|
|
throw new \RuntimeException("Invalid Spec425 fixture [{$family}/{$name}].");
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
public static function syncDefaults(): void
|
|
{
|
|
app(ResourceTypeRegistry::class)->syncDefaults();
|
|
app(SupportedScopeResolver::class)->syncDefaults();
|
|
}
|
|
|
|
public static function resourceType(string $canonicalType): TenantConfigurationResourceType
|
|
{
|
|
return TenantConfigurationResourceType::query()
|
|
->active()
|
|
->where('canonical_type', $canonicalType)
|
|
->orderBy('source_class')
|
|
->firstOrFail();
|
|
}
|
|
|
|
public static function createRun(ManagedEnvironment $environment, ProviderConnection $connection): OperationRun
|
|
{
|
|
return OperationRun::factory()
|
|
->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' => ['conditionalAccessPolicy', 'securityDefaults'],
|
|
'required_capability' => 'evidence.manage',
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
* @param array<string, mixed> $resourceOverrides
|
|
* @param array<string, mixed> $evidenceOverrides
|
|
*/
|
|
public static function createResourceWithEvidence(
|
|
ManagedEnvironment $environment,
|
|
ProviderConnection $connection,
|
|
string $canonicalType,
|
|
array $payload,
|
|
array $resourceOverrides = [],
|
|
array $evidenceOverrides = [],
|
|
): TenantConfigurationResource {
|
|
$resourceType = self::resourceType($canonicalType);
|
|
$normalizedPayload = self::normalizedPayload($payload);
|
|
$payloadHash = self::payloadHash($normalizedPayload);
|
|
$run = self::createRun($environment, $connection);
|
|
$sourceResourceId = (string) ($payload['id'] ?? $canonicalType);
|
|
|
|
/** @var TenantConfigurationResource $resource */
|
|
$resource = TenantConfigurationResource::factory()->create(array_replace([
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
'resource_type_id' => (int) $resourceType->getKey(),
|
|
'source_class' => $resourceType->source_class,
|
|
'canonical_type' => $canonicalType,
|
|
'canonical_resource_key' => "{$canonicalType}:graph_object_id:{$sourceResourceId}",
|
|
'canonical_key_kind' => CanonicalKeyKind::GraphObjectId->value,
|
|
'source_resource_id' => $sourceResourceId,
|
|
'source_display_name' => (string) ($payload['displayName'] ?? $payload['name'] ?? $canonicalType),
|
|
'source_metadata' => [
|
|
'source_contract_key' => self::sourceContractKey($canonicalType),
|
|
'source_endpoint' => self::sourceEndpoint($canonicalType),
|
|
'source_version' => 'v1.0',
|
|
],
|
|
'identity_strategy' => $canonicalType === 'securityDefaults'
|
|
? 'graph.security_defaults.v1'
|
|
: 'graph.conditional_access_policy.v1',
|
|
'source_identity' => [
|
|
'primary_value' => $sourceResourceId,
|
|
'strategy_identifier' => $canonicalType === 'securityDefaults'
|
|
? 'graph.security_defaults.v1'
|
|
: 'graph.conditional_access_policy.v1',
|
|
],
|
|
'secondary_identity_keys' => (object) [],
|
|
'identity_diagnostics' => (object) [],
|
|
'latest_evidence_state' => EvidenceState::ContentBacked->value,
|
|
'latest_identity_state' => IdentityState::Stable->value,
|
|
'latest_claim_state' => ClaimState::InternalOnly->value,
|
|
'latest_payload_hash' => $payloadHash,
|
|
'latest_captured_at' => now(),
|
|
], $resourceOverrides));
|
|
|
|
/** @var TenantConfigurationResourceEvidence $evidence */
|
|
$evidence = TenantConfigurationResourceEvidence::factory()->create(array_replace([
|
|
'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) $run->getKey(),
|
|
'source_contract_key' => self::sourceContractKey($canonicalType),
|
|
'source_endpoint' => self::sourceEndpoint($canonicalType),
|
|
'source_version' => 'v1.0',
|
|
'source_schema_hash' => hash('sha256', $canonicalType),
|
|
'source_metadata' => [
|
|
'source_contract_key' => self::sourceContractKey($canonicalType),
|
|
'source_endpoint' => self::sourceEndpoint($canonicalType),
|
|
'source_version' => 'v1.0',
|
|
],
|
|
'raw_payload' => $payload,
|
|
'normalized_payload' => $normalizedPayload,
|
|
'payload_hash' => $payloadHash,
|
|
'permission_context' => ['scopes_granted' => ['Policy.Read.All']],
|
|
'evidence_state' => EvidenceState::ContentBacked->value,
|
|
'coverage_level' => CoverageLevel::Renderable->value,
|
|
'capture_outcome' => CaptureOutcome::Captured->value,
|
|
'captured_at' => now(),
|
|
], $evidenceOverrides));
|
|
|
|
$resource->forceFill([
|
|
'latest_evidence_id' => (int) $evidence->getKey(),
|
|
'latest_payload_hash' => (string) $evidence->payload_hash,
|
|
'latest_captured_at' => $evidence->captured_at,
|
|
])->save();
|
|
|
|
return $resource->refresh();
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function normalizedPayload(array $payload): array
|
|
{
|
|
$redacted = app(CoveragePayloadRedactor::class)->redact($payload);
|
|
|
|
return is_array($redacted) ? $redacted : [];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
public static function payloadHash(array $payload): string
|
|
{
|
|
return hash('sha256', json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR));
|
|
}
|
|
|
|
public static function sourceContractKey(string $canonicalType): string
|
|
{
|
|
return $canonicalType === 'securityDefaults' ? 'securityDefaults' : 'conditionalAccessPolicy';
|
|
}
|
|
|
|
public static function sourceEndpoint(string $canonicalType): string
|
|
{
|
|
return $canonicalType === 'securityDefaults'
|
|
? '/policies/identitySecurityDefaultsEnforcementPolicy'
|
|
: '/identity/conditionalAccess/policies';
|
|
}
|
|
}
|